<html lang="zh-CN" data-bs-theme="auto"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title data-i18n-title="pageTitle">DocuTranslate - 交互式文档翻译</title>    <link rel="icon" href="/static/favicon.ico" type="image/x-icon">    <!-- Bootstrap CSS -->    <link href="/static/bootstrap.css" rel="stylesheet" crossorigin="anonymous">    <!-- Bootstrap Icons -->    <link rel="stylesheet" href="/static/bootstrap-icons.css">    <style>        body {            background-color: var(--bs-body-bg);        }        button:focus {            outline: none !important;            box-shadow: none !important;        }        .main-container {            display: flex;            flex-direction: column;            height: 100vh;            padding-top: 1rem;            padding-bottom: 1rem;        }        .settings-panel {            height: calc(100vh - 2rem);            overflow-y: auto;            padding-right: 15px; /* for scrollbar */        }        .task-area {            height: calc(100vh - 2rem);            overflow-y: auto;        }        .task-card {            transition: all 0.3s ease-in-out;        }        .task-id-placeholder {            color: var(--bs-secondary-color);            font-style: italic;        }        .log-area {            height: 150px;            background-color: var(--bs-tertiary-bg);            border: 1px solid var(--bs-border-color);            border-radius: .375rem;            font-family: monospace;            font-size: 0.8rem;            padding: 10px;            overflow-y: auto;            white-space: pre-wrap;            word-break: break-all;        }        /* [NEW] Wrapper for log area to position the copy button */        .log-area-wrapper {            position: relative;        }        /* [NEW] Styles for the copy button */        .copy-log-btn {            position: absolute;            bottom: 5px;            right: 5px;            z-index: 10;            opacity: 0.5;            transition: opacity 0.2s ease-in-out, background-color 0.2s ease-in-out, border-color 0.2s ease-in-out;            padding: 0.1rem 0.4rem;            line-height: 1;        }        .copy-log-btn:hover {            opacity: 1;        }        .file-drop-area {            border: 2px dashed var(--bs-secondary-bg);            border-radius: .375rem;            padding: 2rem;            text-align: center;            cursor: pointer;            transition: all 0.2s ease-in-out;            background-color: var(--bs-body-bg);        }        .file-drop-area.drag-over {            border-color: var(--bs-primary);            background-color: var(--bs-secondary-bg);        }        .file-drop-area.file-selected {            border-style: solid;            border-color: var(--bs-success);            background-color: var(--bs-success-bg-subtle);        }        .file-drop-area.input-error {            border-color: var(--bs-danger);        }        .file-name-display.input-error-text {            color: var(--bs-danger);            font-weight: bold;        }        #printFrame, #translatedPreviewFrame {            border: none;            width: 100%;        }        #previewOffcanvas {            --bs-offcanvas-width: 95vw;            max-width: 1600px;        }        .preview-split-container {            display: flex;            flex-direction: row;            height: 90%;        }        .preview-pane-wrapper {            display: flex;            flex-direction: column;            overflow: hidden; /* Important for split.js */        }        .preview-pane-wrapper h6 {            flex-shrink: 0;            padding: 0.25rem;        }        .preview-pane-wrapper .preview-pane {            flex-grow: 1; /* Make the inner pane grow */            border: 1px solid var(--bs-border-color);            border-radius: .375rem;            overflow: auto;        }        .gutter {            background-color: var(--bs-tertiary-bg);            background-repeat: no-repeat;            background-position: 50%;        }        .gutter.gutter-horizontal {            cursor: col-resize;            border-left: 1px solid var(--bs-border-color);            border-right: 1px solid var(--bs-border-color);        }        .gutter.gutter-vertical {            cursor: row-resize;            border-top: 1px solid var(--bs-border-color);            border-bottom: 1px solid var(--bs-border-color);        }        .preview-pane iframe {            width: 100%;            height: 100%; /* Make iframe fill the pane completely */            border: none;            margin: 0;            padding: 0;        }        .preview-pane pre {            /* Let the content determine the width, the parent will scroll */            margin: 0;            padding: 1rem; /* Add some padding for text content */            background-color: var(--bs-body-bg);            white-space: pre; /* Do not wrap long lines, show horizontal scrollbar on parent */        }        .slider-reset-btn {            visibility: hidden;        }        .bottom-left-controls {            position: fixed;            bottom: 1rem;            left: 1rem;            z-index: 1050;            display: flex;            gap: 0.5rem;        }        .project-info p {            letter-spacing: 0.8px;            line-height: 1.6;        }        /* [NEW] Style for the step number */        .step-number {            margin-right: 0.25rem;        }        @media (max-width: 991.98px) {            .main-container {                height: auto;                padding-bottom: 6rem;            }            .settings-panel, .task-area {                height: auto;                overflow-y: visible;            }            .settings-panel {                padding-right: 0;                margin-bottom: 2rem;            }        }        @media (max-width: 767.98px) {            .task-card .col-md-7 {                margin-top: 1.5rem;            }            #previewOffcanvas {                --bs-offcanvas-width: 100vw;            }        }    </style></head><body><div class="container-fluid main-container">    <div class="row gx-4">        <!-- Left: Settings Panel -->        <div class="col-lg-4">            <div class="settings-panel">                <div class="d-flex justify-content-between align-items-center mb-3">                    <div class="d-flex align-items-center">                        <h4 class="mb-0 me-3 fw-bold">DocuTranslate</h4>                        <div class="btn-group">                            <button type="button" class="btn btn-sm btn-outline-info" data-bs-toggle="modal"                                    data-bs-target="#tutorialModal">                                <i class="bi bi-question-circle-fill me-1"></i><span data-i18n="tutorialBtn">教程</span>                            </button>                            <button type="button" class="btn btn-sm btn-outline-warning" data-bs-toggle="modal"                                    data-bs-target="#contributorsModal">                                <i class="bi bi-people-fill me-1"></i><span                                    data-i18n="projectContributeBtn">项目协作</span>                            </button>                        </div>                    </div>                </div>                <form id="translateForm">                    <div class="accordion" id="settingsAccordion">                        <!-- Workflow Selection -->                        <div class="accordion-item" id="workflowSettingsContainer">                            <h2 class="accordion-header" id="headingZero">                                <button class="accordion-button" type="button" data-bs-toggle="collapse"                                        data-bs-target="#collapseZero" aria-expanded="true"                                        aria-controls="collapseZero">                                    <!-- MODIFIED HERE -->                                    <strong><i class="bi bi-diagram-3 me-2"></i><span                                            data-i18n="workflowTitle">选择工作流</span></strong>                                </button>                            </h2>                            <div id="collapseZero" class="accordion-collapse collapse show"                                 aria-labelledby="headingZero">                                <div class="accordion-body">                                    <select class="form-select" id="workflowTypeSelect">                                        <option value="markdown_based" data-i18n="workflowOptionMarkdown">转Markdown再翻译                                            (.pdf/.md/.png等)                                        </option>                                        <option value="txt" data-i18n="workflowOptionTxt">纯文本翻译 (.txt)</option>                                        <option value="epub" data-i18n="workflowOptionEpub">EPUB翻译 (.epub)</option>                                        <option value="docx" data-i18n="workflowOptionDocx">DOCX翻译 (.docx)</option>                                        <option value="xlsx" data-i18n="workflowOptionXlsx">XLSX翻译 (.xlsx/.csv)                                        </option>                                        <option value="srt" data-i18n="workflowOptionSrt">SRT字幕翻译 (.srt)</option>                                        <option value="ass" data-i18n="workflowOptionAss">ASS字幕翻译 (.ass)</option>                                        <option value="json" data-i18n="workflowOptionJson">JSON翻译 (.json)</option>                                        <option value="html" data-i18n="workflowOptionHtml">HTML翻译 (.html)</option>                                    </select>                                    <div class="form-check form-switch mt-2">                                        <input class="form-check-input" type="checkbox" role="switch"                                               id="autoWorkflowSwitch">                                        <label class="form-check-label" for="autoWorkflowSwitch"                                               data-i18n="autoWorkflowLabel">自动选择工作流</label>                                    </div>                                </div>                            </div>                        </div>                        <!-- [NEW] TXT Settings Container -->                        <div class="accordion-item" id="txtSettingsContainer" style="display: none;">                            <h2 class="accordion-header" id="headingTxt">                                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"                                        data-bs-target="#collapseTxt" aria-expanded="false"                                        aria-controls="collapseTxt">                                    <!-- MODIFIED HERE -->                                    <strong id="txtSettingsTitle"><i class="bi bi-filetype-txt me-2"></i><span                                            data-i18n="txtSettingsTitleText">TXT翻译选项</span></strong>                                </button>                            </h2>                            <div id="collapseTxt" class="accordion-collapse collapse" aria-labelledby="headingTxt">                                <div class="accordion-body">                                    <div class="mb-3">                                        <label for="txt_insert_mode" class="form-label"                                               data-i18n="insertModeLabel">插入模式</label>                                        <select class="form-select" id="txt_insert_mode" name="insert_mode">                                            <option value="replace" data-i18n="insertModeReplace">替换原文 (Replace)                                            </option>                                            <option value="append" data-i18n="insertModeAppend">附加到原文后 (Append)                                            </option>                                            <option value="prepend" data-i18n="insertModePrepend">附加到原文前                                                (Prepend)                                            </option>                                        </select>                                        <div class="form-text" data-i18n="insertModeHelpTxt">                                            选择如何将翻译后的文本插入。                                        </div>                                    </div>                                    <div class="mb-3" id="txtSeparatorGroup" style="display: none;">                                        <label for="txt_separator" class="form-label"                                               data-i18n="separatorLabel">分隔符</label>                                        <input type="text" class="form-control" id="txt_separator" name="separator"                                               data-i18n-placeholder="separatorPlaceholderSimple"                                               placeholder="例如: \n---\n">                                        <div class="form-text" data-i18n="separatorHelp">                                            当插入模式为附加或前置时，用于分隔原文和译文的字符。<code>\n</code> 代表换行。                                        </div>                                    </div>                                </div>                            </div>                        </div>                        <!-- [NEW] DOCX Settings Container -->                        <div class="accordion-item" id="docxSettingsContainer" style="display: none;">                            <h2 class="accordion-header" id="headingDocx">                                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"                                        data-bs-target="#collapseDocx" aria-expanded="false"                                        aria-controls="collapseDocx">                                    <!-- MODIFIED HERE -->                                    <strong id="docxSettingsTitle"><i class="bi bi-file-earmark-word me-2"></i><span                                            data-i18n="docxSettingsTitleText">DOCX翻译选项</span></strong>                                </button>                            </h2>                            <div id="collapseDocx" class="accordion-collapse collapse" aria-labelledby="headingDocx">                                <div class="accordion-body">                                    <div class="mb-3">                                        <label for="docx_insert_mode" class="form-label" data-i18n="insertModeLabel">插入模式</label>                                        <select class="form-select" id="docx_insert_mode" name="insert_mode">                                            <option value="replace" data-i18n="insertModeReplace">替换原文 (Replace)                                            </option>                                            <option value="append" data-i18n="insertModeAppend">附加到原文后 (Append)                                            </option>                                            <option value="prepend" data-i18n="insertModePrepend">附加到原文前                                                (Prepend)                                            </option>                                        </select>                                        <div class="form-text" data-i18n="insertModeHelpDocx">                                            选择如何将翻译后的文本插入。                                        </div>                                    </div>                                    <div class="mb-3" id="docxSeparatorGroup" style="display: none;">                                        <label for="docx_separator" class="form-label"                                               data-i18n="separatorLabel">分隔符</label>                                        <input type="text" class="form-control" id="docx_separator" name="separator"                                               data-i18n-placeholder="separatorPlaceholderStructured"                                               placeholder="例如: ---">                                        <div class="form-text" data-i18n="separatorHelpDocx">                                            附加模式下译文会另起一段。此项用于在原文和译文段落之间添加额外内容。<code>\n</code> 可在分隔符内部换行。                                        </div>                                    </div>                                </div>                            </div>                        </div>                        <!-- XLSX Settings Container -->                        <div class="accordion-item" id="xlsxSettingsContainer" style="display: none;">                            <h2 class="accordion-header" id="headingXlsx">                                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"                                        data-bs-target="#collapseXlsx" aria-expanded="false"                                        aria-controls="collapseXlsx">                                    <!-- MODIFIED HERE -->                                    <strong id="xlsxSettingsTitle"><i                                            class="bi bi-file-earmark-spreadsheet me-2"></i><span                                            data-i18n="xlsxSettingsTitleText">XLSX翻译选项</span></strong>                                </button>                            </h2>                            <div id="collapseXlsx" class="accordion-collapse collapse" aria-labelledby="headingXlsx">                                <div class="accordion-body">                                    <div class="mb-3">                                        <label for="xlsx_insert_mode" class="form-label" data-i18n="insertModeLabel">插入模式</label>                                        <select class="form-select" id="xlsx_insert_mode" name="insert_mode">                                            <option value="replace" data-i18n="insertModeReplace">替换原文 (Replace)                                            </option>                                            <option value="append" data-i18n="insertModeAppend">附加到原文后 (Append)                                            </option>                                            <option value="prepend" data-i18n="insertModePrepend">附加到原文前                                                (Prepend)                                            </option>                                        </select>                                        <div class="form-text" data-i18n="insertModeHelpXlsx">                                            选择如何将翻译后的文本插入到单元格中。                                        </div>                                    </div>                                    <div class="mb-3" id="xlsxSeparatorGroup" style="display: none;">                                        <label for="xlsx_separator" class="form-label"                                               data-i18n="separatorLabel">分隔符</label>                                        <input type="text" class="form-control" id="xlsx_separator" name="separator"                                               data-i18n-placeholder="separatorPlaceholderSimple"                                               placeholder="例如: \n---\n">                                        <div class="form-text" data-i18n="separatorHelp">                                            当插入模式为附加或前置时，用于分隔原文和译文的字符。<code>\n</code> 代表换行。                                        </div>                                    </div>                                    <div class="mb-3">                                        <label for="xlsx_translate_regions" class="form-label"                                               data-i18n="xlsxTranslateRegionsLabel">翻译区域 (可选)</label>                                        <textarea class="form-control" id="xlsx_translate_regions"                                                  name="translate_regions" rows="3"                                                  data-i18n-placeholder="xlsxTranslateRegionsPlaceholder"                                                  placeholder="每行一个区域, 例如:Sheet1!A1:B10（不指定表名则对所有表生效）"></textarea>                                    </div>                                </div>                            </div>                        </div>                        <!-- [NEW] SRT Settings Container -->                        <div class="accordion-item" id="srtSettingsContainer" style="display: none;">                            <h2 class="accordion-header" id="headingSrt">                                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"                                        data-bs-target="#collapseSrt" aria-expanded="false"                                        aria-controls="collapseSrt">                                    <!-- MODIFIED HERE -->                                    <strong id="srtSettingsTitle"><i class="bi bi-file-text me-2"></i><span                                            data-i18n="srtSettingsTitleText">SRT翻译选项</span></strong>                                </button>                            </h2>                            <div id="collapseSrt" class="accordion-collapse collapse" aria-labelledby="headingSrt">                                <div class="accordion-body">                                    <div class="mb-3">                                        <label for="srt_insert_mode" class="form-label"                                               data-i18n="insertModeLabel">插入模式</label>                                        <select class="form-select" id="srt_insert_mode" name="insert_mode">                                            <option value="replace" data-i18n="insertModeReplace">替换原文 (Replace)                                            </option>                                            <option value="append" data-i18n="insertModeAppend">附加到原文后 (Append)                                            </option>                                            <option value="prepend" data-i18n="insertModePrepend">附加到原文前                                                (Prepend)                                            </option>                                        </select>                                        <div class="form-text" data-i18n="insertModeHelpSrt">                                            选择如何将翻译后的文本插入。                                        </div>                                    </div>                                    <div class="mb-3" id="srtSeparatorGroup" style="display: none;">                                        <label for="srt_separator" class="form-label"                                               data-i18n="separatorLabel">分隔符</label>                                        <input type="text" class="form-control" id="srt_separator" name="separator"                                               data-i18n-placeholder="separatorPlaceholderSimple"                                               placeholder="例如: \n---\n">                                        <div class="form-text" data-i18n="separatorHelp">                                            当插入模式为附加或前置时，用于分隔原文和译文的字符。<code>\n</code> 代表换行。                                        </div>                                    </div>                                </div>                            </div>                        </div>                        <!-- [NEW] EPUB Settings Container -->                        <div class="accordion-item" id="epubSettingsContainer" style="display: none;">                            <h2 class="accordion-header" id="headingEpub">                                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"                                        data-bs-target="#collapseEpub" aria-expanded="false"                                        aria-controls="collapseEpub">                                    <!-- MODIFIED HERE -->                                    <strong id="epubSettingsTitle"><i class="bi bi-book me-2"></i><span                                            data-i18n="epubSettingsTitleText">EPUB翻译选项</span></strong>                                </button>                            </h2>                            <div id="collapseEpub" class="accordion-collapse collapse" aria-labelledby="headingEpub">                                <div class="accordion-body">                                    <div class="mb-3">                                        <label for="epub_insert_mode" class="form-label" data-i18n="insertModeLabel">插入模式</label>                                        <select class="form-select" id="epub_insert_mode" name="insert_mode">                                            <option value="replace" data-i18n="insertModeReplace">替换原文 (Replace)                                            </option>                                            <option value="append" data-i18n="insertModeAppend">附加到原文后 (Append)                                            </option>                                            <option value="prepend" data-i18n="insertModePrepend">附加到原文前                                                (Prepend)                                            </option>                                        </select>                                        <div class="form-text" data-i18n="insertModeHelpEpub">                                            选择如何将翻译后的文本插入。                                        </div>                                    </div>                                    <div class="mb-3" id="epubSeparatorGroup" style="display: none;">                                        <label for="epub_separator" class="form-label"                                               data-i18n="separatorLabel">分隔符</label>                                        <input type="text" class="form-control" id="epub_separator" name="separator"                                               data-i18n-placeholder="separatorPlaceholderStructured"                                               placeholder="例如: ---">                                        <div class="form-text" data-i18n="separatorHelpEpub">                                            附加模式下译文会另起一段。此项用于在原文和译文区块之间添加额外内容。<code>\n</code> 可在分隔符内部换行。                                        </div>                                    </div>                                </div>                            </div>                        </div>                        <!-- [NEW] HTML Settings Container -->                        <div class="accordion-item" id="htmlSettingsContainer" style="display: none;">                            <h2 class="accordion-header" id="headingHtml">                                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"                                        data-bs-target="#collapseHtml" aria-expanded="false"                                        aria-controls="collapseHtml">                                    <!-- MODIFIED HERE -->                                    <strong id="htmlSettingsTitle"><i class="bi bi-filetype-html me-2"></i><span                                            data-i18n="htmlSettingsTitleText">HTML翻译选项</span></strong>                                </button>                            </h2>                            <div id="collapseHtml" class="accordion-collapse collapse" aria-labelledby="headingHtml">                                <div class="accordion-body">                                    <div class="mb-3">                                        <label for="html_insert_mode" class="form-label" data-i18n="insertModeLabel">插入模式</label>                                        <select class="form-select" id="html_insert_mode" name="insert_mode">                                            <option value="replace" data-i18n="insertModeReplace">替换原文 (Replace)                                            </option>                                            <option value="append" data-i18n="insertModeAppend">附加到原文后 (Append)                                            </option>                                            <option value="prepend" data-i18n="insertModePrepend">附加到原文前                                                (Prepend)                                            </option>                                        </select>                                        <div class="form-text" data-i18n="insertModeHelpHtml">                                            选择如何将翻译后的文本插入。                                        </div>                                    </div>                                    <div class="mb-3" id="htmlSeparatorGroup" style="display: none;">                                        <label for="html_separator" class="form-label"                                               data-i18n="separatorLabel">分隔符</label>                                        <input type="text" class="form-control" id="html_separator" name="separator"                                               data-i18n-placeholder="separatorPlaceholderStructured"                                               placeholder="例如: ---">                                        <div class="form-text" data-i18n="separatorHelpHtml">                                            附加模式下译文会另起一段。此项用于在原文和译文区块之间添加额外内容。<code>\n</code> 可在分隔符内部换行。                                        </div>                                    </div>                                </div>                            </div>                        </div>                        <div class="accordion-item" id="assSettingsContainer" style="display: none;">                            <h2 class="accordion-header" id="headingAss">                                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"                                        data-bs-target="#collapseAss" aria-expanded="false"                                        aria-controls="collapseAss">                                    <strong id="assSettingsTitle"><i class="bi bi-file-easel me-2"></i><span                                            data-i18n="assSettingsTitleText">ASS翻译选项</span></strong>                                </button>                            </h2>                            <div id="collapseAss" class="accordion-collapse collapse" aria-labelledby="headingAss">                                <div class="accordion-body">                                    <div class="mb-3">                                        <label for="ass_insert_mode" class="form-label"                                               data-i18n="insertModeLabel">插入模式</label>                                        <select class="form-select" id="ass_insert_mode" name="insert_mode">                                            <option value="replace" data-i18n="insertModeReplace">替换原文 (Replace)                                            </option>                                            <option value="append" data-i18n="insertModeAppend">附加到原文后 (Append)                                            </option>                                            <option value="prepend" data-i18n="insertModePrepend">附加到原文前                                                (Prepend)                                            </option>                                        </select>                                        <div class="form-text" data-i18n="insertModeHelpAss">                                            选择如何将翻译后的文本插入。                                        </div>                                    </div>                                    <div class="mb-3" id="assSeparatorGroup" style="display: none;">                                        <label for="ass_separator" class="form-label"                                               data-i18n="separatorLabel">分隔符</label>                                        <input type="text" class="form-control" id="ass_separator" name="separator"                                               data-i18n-placeholder="separatorPlaceholderAss"                                               placeholder="例如: \N (换行符)">                                        <div class="form-text" data-i18n="separatorHelpAss">                                            当插入模式为附加或前置时，用于分隔原文和译文的字符。<code>\N</code>                                            是ASS格式的换行符。                                        </div>                                    </div>                                </div>                            </div>                        </div>                        <!-- JSON Paths Settings Container -->                        <div class="accordion-item" id="jsonSettingsContainer" style="display: none;">                            <h2 class="accordion-header" id="headingJson">                                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"                                        data-bs-target="#collapseJson" aria-expanded="false"                                        aria-controls="collapseJson">                                    <strong id="jsonSettingsTitle"><i class="bi bi-signpost-split me-2"></i><span                                            data-i18n="jsonSettingsTitleText">JSON路径配置</span></strong>                                </button>                            </h2>                            <div id="collapseJson" class="accordion-collapse collapse" aria-labelledby="headingJson">                                <div class="accordion-body">                                    <div class="mb-3">                                        <label for="json_paths_textarea" class="form-label" data-i18n="jsonPathLabel">需要翻译的JSON路径</label>                                        <textarea class="form-control" id="json_paths_textarea" name="json_paths"                                                  rows="4" required                                                  data-i18n-placeholder="jsonPathPlaceholder" placeholder="每行一个路径, 例如:$.name$.*"></textarea>                                        <div class="form-text" data-i18n="jsonPathHelp">                                            采用<code>jsonpath-ng</code>的路径选择语法，每一行表示一个json路径。                                            将翻译路径匹配对象内的所有字符串                                        </div>                                    </div>                                </div>                            </div>                        </div>                        <!-- Parsing Engine Settings Container -->                        <div class="accordion-item" id="parsingSettingsContainer">                            <h2 class="accordion-header" id="headingOne">                                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"                                        data-bs-target="#collapseOne" aria-expanded="false" aria-controls="collapseOne">                                    <!-- MODIFIED HERE -->                                    <strong id="parsingSettingsTitle"><i                                            class="bi bi-file-earmark-binary me-2"></i><span                                            data-i18n="parsingSettingsTitleText">解析配置</span></strong>                                </button>                            </h2>                            <div id="collapseOne" class="accordion-collapse collapse" aria-labelledby="headingOne">                                <div class="accordion-body">                                    <div class="mb-3">                                        <label for="convert_engine" class="form-label" data-i18n="parsingEngineLabel">解析引擎</label>                                        <select class="form-select" id="convert_engine" name="convert_engine">                                            <!-- Options will be populated by JS -->                                        </select>                                        <div class="form-text" data-i18n="parsingEngineHelp">                                            如果上传的文件本身是.md格式，此项可不选。                                        </div>                                    </div>                                    <div class="mb-3" id="mineruTokenGroup" style="display: none;">                                        <label for="mineru_token" class="form-label">                                            Mineru Token                                            <a href="https://mineru.net/apiManage/token" target="_blank" class="ms-1"                                               data-i18n-title="getMineruTokenTitle" title="获取Mineru Token"><i                                                    class="bi bi-box-arrow-up-right"></i></a>                                        </label>                                        <div class="input-group">                                            <input type="password" class="form-control" id="mineru_token"                                                   name="mineru_token" data-i18n-placeholder="mineruTokenPlaceholder"                                                   placeholder="使用Mineru引擎时需要">                                            <button class="btn btn-outline-secondary toggle-password" type="button"                                                    data-target="mineru_token">                                                <i class="bi bi-eye-slash"></i>                                            </button>                                        </div>                                    </div>                                    <!-- NEW: Mineru Model Version -->                                    <div class="mb-3" id="mineruModelVersionGroup" style="display: none;">                                        <label for="model_version" class="form-label" data-i18n="modelVersionLabel">Mineru                                            模型版本</label>                                        <select class="form-select" id="model_version" name="model_version">                                            <option value="vlm" data-i18n="modelVersionVlm">VLM</option>                                            <option value="pipeline" data-i18n="modelVersionPipline">Pipeline</option>                                        </select>                                        <div class="form-text" data-i18n="modelVersionHelp">                                            mineru VLM是更新的内测模型。                                        </div>                                    </div>                                    <!-- NEW: Mineru Deploy Service Settings -->                                    <div id="mineruDeployGroup" class="border p-3 rounded" style="display: none;">                                        <div class="mb-3">                                            <label for="mineru_deploy_base_url" class="form-label"                                                   data-i18n="mineruDeployBaseUrlLabel">服务地址 (Base URL)</label>                                            <input type="url" class="form-control" id="mineru_deploy_base_url"                                                   name="mineru_deploy_base_url" required                                                   data-i18n-placeholder="mineruDeployBaseUrlPlaceholder"                                                   placeholder="例如: http://127.0.0.1:8000">                                        </div>                                        <div class="mb-3">                                            <label for="mineru_deploy_backend" class="form-label"                                                   data-i18n="mineruDeployBackendLabel">后端类型</label>                                            <select class="form-select" id="mineru_deploy_backend"                                                    name="mineru_deploy_backend">                                                <option value="pipeline" data-i18n="mineruDeployBackendPipeline">Pipeline</option>                                                <option value="transformers" data-i18n="mineruDeployBackendTransformers">Transformers</option>                                                <option value="mlx-engine" data-i18n="mineruDeployBackendMlx">MLX Engine</option>                                                <option value="vllm-engine" data-i18n="mineruDeployBackendVllm">VLLM Engine</option>                                                <option value="vllm-async-engine" data-i18n="mineruDeployBackendVllmAsync">VLLM Async Engine</option>                                            </select>                                        </div>                                        <div class="row">                                            <div class="col-md-6 mb-3">                                                <label for="mineru_deploy_start_page_id" class="form-label"                                                       data-i18n="mineruDeployStartPageLabel">起始页面</label>                                                <input type="number" class="form-control"                                                       id="mineru_deploy_start_page_id"                                                       name="mineru_deploy_start_page_id" value="0" min="0">                                            </div>                                            <div class="col-md-6 mb-3">                                                <label for="mineru_deploy_end_page_id" class="form-label"                                                       data-i18n="mineruDeployEndPageLabel">结束页面</label>                                                <input type="number" class="form-control"                                                       id="mineru_deploy_end_page_id" name="mineru_deploy_end_page_id"                                                       value="99999" min="0">                                            </div>                                        </div>                                        <div class="form-check form-switch mb-2">                                            <input class="form-check-input" type="checkbox" role="switch"                                                   id="mineru_deploy_formula_enable"                                                   name="mineru_deploy_formula_enable" checked>                                            <label class="form-check-label" for="mineru_deploy_formula_enable"                                                   data-i18n="mineruDeployFormulaEnableLabel">启用公式解析</label>                                        </div>                                    </div>                                    <!-- Shared OCR switches -->                                    <div class="border-top mt-3 pt-3">                                        <div class="form-check form-switch mb-2" id="formulaOcrSwitchContainer">                                            <input class="form-check-input" type="checkbox" role="switch"                                                   id="formula_ocr"                                                   name="formula_ocr" checked>                                            <label class="form-check-label" for="formula_ocr"                                                   data-i18n="formulaOcrLabel">公式识别</label>                                        </div>                                        <div class="form-check form-switch mb-2" id="codeOcrSwitchContainer">                                            <input class="form-check-input" type="checkbox" role="switch" id="code_ocr"                                                   name="code_ocr" checked>                                            <label class="form-check-label" for="code_ocr"                                                   data-i18n="codeOcrLabel">代码识别</label>                                        </div>                                    </div>                                </div>                            </div>                        </div>                        <!-- AI Settings -->                        <div class="accordion-item">                            <h2 class="accordion-header" id="headingTwo">                                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"                                        data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">                                    <!-- MODIFIED HERE -->                                    <strong id="aiSettingsTitle"><i class="bi bi-robot me-2"></i><span                                            data-i18n="aiSettingsTitleText">翻译模型</span></strong>                                </button>                            </h2>                            <div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo">                                <div class="accordion-body">                                    <!-- [MODIFIED] Translation Mode to Checkbox -->                                    <div class="form-check form-switch mb-3">                                        <input class="form-check-input" type="checkbox" role="switch"                                               id="skipTranslationSwitch">                                        <label class="form-check-label" for="skipTranslationSwitch"                                               data-i18n="skipTranslationLabel">跳过翻译</label>                                    </div>                                    <!-- Wrapper for AI Model settings -->                                    <div id="aiModelSettingsContainer">                                        <div>                                            <label for="platform_select" class="form-label"                                                   data-i18n="platformLabel">选择平台</label>                                            <select class="form-select" id="platform_select">                                                <option value="custom" data-i18n="platformCustom">自定义接口</option>                                                <option value="https://api.302.ai/v1">302.AI</option>                                                <option value="https://api.openai.com/v1">OpenAI</option>                                                <option value="https://generativelanguage.googleapis.com/v1beta/openai/">                                                    Gemini                                                </option>                                                <option value="https://api.deepseek.com/v1">DeepSeek</option>                                                <option value="https://dashscope.aliyuncs.com/compatible-mode/v1">                                                    阿里云百炼(DashScope)                                                </option>                                                <option value="https://ark.cn-beijing.volces.com/api/v3">火山引擎(volces)                                                </option>                                                <option value="https://api.siliconflow.cn/v1">硅基流动(siliconflow CN)                                                </option>                                                <option value="https://open.bigmodel.cn/api/paas/v4">智谱AI(bigmodel                                                    CN)                                                </option>                                                <option value="https://www.dmxapi.cn/v1">DMXAPI_CN                                                </option>                                                <option value="https://www.dmxapi.com/v1">DMXAPI_GLOBAL                                                </option>                                                <option value="https://ai.juguang.chat/v1">聚光AI(juguang CN)                                                </option>                                                <option value="https://openrouter.ai/api/v1">OpenRouter</option>                                                <option value="http://127.0.0.1:1234/v1">LM Studio</option>                                                <option value="http://127.0.0.1:11434/v1">Ollama</option>                                            </select>                                        </div>                                        <div class="form-text mb-3" id="main_base_url_display_container">                                            Base URL: <code id="main_base_url_display"></code>                                        </div>                                        <div class="mb-3" id="baseUrlGroup">                                            <label for="base_url" class="form-label" data-i18n="baseUrlLabel">API 地址                                                (Base                                                URL)</label>                                            <input type="url" class="form-control" id="base_url" name="base_url"                                                   required                                                   data-i18n-placeholder="baseUrlPlaceholder"                                                   placeholder="OpenAi兼容地址">                                        </div>                                        <div class="mb-3">                                            <label for="api_key" class="form-label">                                                API Key                                                <a href="#" target="_blank" class="ms-1" id="api_href"                                                   data-i18n-title="getApiKeyTitle" title="获取API Key"><i                                                        class="bi bi-box-arrow-up-right"></i></a>                                                <span class="ms-2 text-muted small" id="api_href_info"></span>                                            </label>                                            <div class="input-group">                                                <input type="password" class="form-control" id="api_key" name="api_key"                                                       data-i18n-placeholder="apiKeyPlaceholder"                                                       placeholder="请输入您的API Key">                                                <button class="btn btn-outline-secondary toggle-password" type="button"                                                        data-target="api_key">                                                    <i class="bi bi-eye-slash"></i>                                                </button>                                            </div>                                        </div>                                        <div class="mb-3">                                            <label for="model_id" class="form-label"                                                   data-i18n="modelIdLabel">模型ID</label>                                            <input type="text" class="form-control" id="model_id" name="model_id"                                                   required                                                   data-i18n-placeholder="modelIdPlaceholder"                                                   placeholder="例如: gpt-4o, glm-4">                                        </div>                                        <div class="form-check form-switch mb-3">                                            <input class="form-check-input" type="checkbox" role="switch"                                                   id="system_proxy_enable" name="system_proxy_enable">                                            <label class="form-check-label" for="system_proxy_enable"                                                   data-i18n="systemProxyLabel">启用系统代理</label>                                        </div>                                    </div>                                </div>                            </div>                        </div>                        <!-- Translation Settings -->                        <div class="accordion-item" id="translationSettingsAccordionItem">                            <h2 class="accordion-header" id="headingThree">                                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"                                        data-bs-target="#collapseThree" aria-expanded="false"                                        aria-controls="collapseThree">                                    <!-- MODIFIED HERE -->                                    <strong id="translationSettingsTitle"><i class="bi bi-translate me-2"></i><span                                            data-i18n="translationSettingsTitleText">翻译配置</span></strong>                                </button>                            </h2>                            <div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree">                                <div class="accordion-body">                                    <div class="mb-3">                                        <label for="to_lang" class="form-label"                                               data-i18n="targetLanguageLabel">目标语言</label>                                        <select class="form-select" id="to_lang" name="to_lang">                                            <option value="Simplified Chinese">中文(简体中文)</option>                                            <option value="English">英文(English)</option>                                            <option value="Spanish">西班牙文(Español)</option>                                            <option value="French">法文(Français)</option>                                            <option value="German">德文(Deutsch)</option>                                            <option value="Japanese">日文(日本語)</option>                                            <option value="Korean">韩文(한국어)</option>                                            <option value="Russian">俄文(Русский)</option>                                            <option value="Portuguese">葡萄牙文(Português)</option>                                            <option value="Arabic">阿拉伯文(العَرَبِيَّة)</option>                                            <option value="Vietnamese">越南文(tiếng Việt)</option>                                            <option value="custom" data-i18n="targetLanguageCustom">其它 (自定义)                                            </option>                                        </select>                                        <div class="mt-2" id="customLangGroup" style="display: none;">                                            <input type="text" class="form-control" id="custom_to_lang"                                                   name="custom_to_lang" data-i18n-placeholder="customLangPlaceholder"                                                   placeholder="请输入目标语言, 例如: Italian">                                        </div>                                    </div>                                    <div class="mb-3">                                        <label class="form-label" data-i18n="thinkingModeLabel">思考模式</label><i                                            class="bi bi-question-circle ms-2 tooltip-icon"                                            data-bs-toggle="tooltip"                                            data-bs-placement="top"                                            data-i18n-title="thinkingModeTooltip"                                            data-bs-title="设置混合推理模型是否进行思考，目前支持智谱的glm4.5系列、火山引擎的seed1.6系列、硅基流动平台、google的gemini系列、302AI(部分)，建议选择禁用思考">                                    </i>                                        <div id="thinkingModeBtnGroup" class="btn-group w-100" role="group"                                             aria-label="Thinking Mode Toggle">                                            <input type="radio" class="btn-check" name="thinking" id="thinkingEnable"                                                   value="enable" autocomplete="off">                                            <label class="btn btn-outline-primary" for="thinkingEnable"                                                   data-i18n="thinkingModeEnable">启用</label>                                            <input type="radio" class="btn-check" name="thinking" id="thinkingDisable"                                                   value="disable" autocomplete="off">                                            <label class="btn btn-outline-primary" for="thinkingDisable"                                                   data-i18n="thinkingModeDisable">禁用(推荐)</label>                                            <input type="radio" class="btn-check" name="thinking" id="thinkingDefault"                                                   value="default" autocomplete="off">                                            <label class="btn btn-outline-primary" for="thinkingDefault"                                                   data-i18n="thinkingModeDefault">默认</label>                                        </div>                                    </div>                                    <div class="mb-3">                                        <label for="custom_prompt" class="form-label" data-i18n="customPromptLabel">自定义Prompt</label>                                        <textarea class="form-control" id="custom_prompt"                                                  name="custom_prompt" rows="3"                                                  data-i18n-placeholder="customPromptPlaceholder"                                                  placeholder="可选，如“人名保持原文不翻译”"></textarea>                                    </div>                                    <!-- MOVED FROM ADVANCED SETTINGS -->                                    <div class="mb-3 border-top pt-3">                                        <label for="chunk-size-slider"                                               class="form-label d-flex justify-content-between">                                            <span><span data-i18n="chunkSizeLabel">分块大小</span>: <span                                                    id="chunk-size-display"></span></span>                                            <button type="button"                                                    class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn"                                                    id="chunk-size-reset" data-i18n="resetBtn">重置                                            </button>                                        </label>                                        <input type="range" class="form-range" min="1000" max="8000" step="100"                                               id="chunk-size-slider" name="chunk_size">                                    </div>                                    <div class="mb-3">                                        <label for="concurrent-slider"                                               class="form-label d-flex justify-content-between">                                            <span><span data-i18n="concurrentLabel">并发数</span>: <span                                                    id="concurrent-display"></span></span>                                            <button type="button"                                                    class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn"                                                    id="concurrent-reset" data-i18n="resetBtn">重置                                            </button>                                        </label>                                        <input type="range" class="form-range" min="1" max="60" step="1"                                               id="concurrent-slider" name="concurrent">                                    </div>                                    <div class="mb-3">                                        <label for="temperature-slider"                                               class="form-label d-flex justify-content-between">                                            <span>Temperature: <span id="temperature-display"></span></span>                                            <button type="button"                                                    class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn"                                                    id="temperature-reset" data-i18n="resetBtn">重置                                            </button>                                        </label>                                        <input type="range" class="form-range" min="0" max="2" step="0.1"                                               id="temperature-slider" name="temperature">                                    </div>                                    <div class="mb-3">                                        <label for="retry-slider" class="form-label d-flex justify-content-between">                                            <span><span data-i18n="retryLabel">重试次数</span>: <span                                                    id="retry-display"></span></span>                                            <button type="button"                                                    class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn"                                                    id="retry-reset" data-i18n="resetBtn">重置                                            </button>                                        </label>                                        <input type="range" class="form-range" min="1" max="6" step="1"                                               id="retry-slider" name="retry">                                    </div>                                </div>                            </div>                        </div>                        <!-- [MODIFIED] Glossary Generation Settings -->                        <div class="accordion-item" id="glossaryGenerationContainer">                            <h2 class="accordion-header" id="headingGlossaryGen">                                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"                                        data-bs-target="#collapseGlossaryGen" aria-expanded="false"                                        aria-controls="collapseGlossaryGen">                                    <!-- MODIFIED HERE -->                                    <strong id="glossaryGenSettingsTitle"><i                                            class="bi bi-journal-bookmark me-2"></i><span data-i18n="glossaryGenTitle">术语表</span></strong>                                </button>                            </h2>                            <div id="collapseGlossaryGen" class="accordion-collapse collapse"                                 aria-labelledby="headingGlossaryGen">                                <div class="accordion-body">                                    <!-- [MOVED] Glossary Section -->                                    <div class="mb-3">                                        <label for="glossary_files" class="form-label" data-i18n="glossaryLabel">术语表                                            (可选)</label>                                        <input class="form-control" type="file" id="glossary_files" multiple                                               accept=".csv">                                        <div class="form-text" data-i18n="glossaryHelp">                                            选择一个或多个CSV文件。文件需包含'src'和'dst'两列标题，分别代表原文和译文。                                        </div>                                        <div class="btn-group mt-2" role="group">                                            <button type="button" class="btn btn-sm btn-outline-info"                                                    id="viewGlossaryBtn">                                                <i class="bi bi-card-list me-1"></i><span data-i18n="viewGlossaryBtn">查看术语表</span>                                            </button>                                            <button type="button" class="btn btn-sm btn-outline-danger"                                                    id="clearGlossaryBtn">                                                <i class="bi bi-trash me-1"></i><span                                                    data-i18n="clearGlossaryBtn">清空</span>                                            </button>                                        </div>                                    </div>                                    <!-- Enable/Disable Switch -->                                    <div class="form-check form-switch mb-3 border-top pt-3">                                        <input class="form-check-input" type="checkbox" role="switch"                                               id="glossary_generate_enable" name="glossary_generate_enable">                                        <label class="form-check-label" for="glossary_generate_enable"                                               data-i18n="glossaryGenEnableLabel">自动生成术语表</label>                                    </div>                                    <!-- Agent Config Options (conditionally displayed) -->                                    <div id="glossaryAgentOptionsContainer" style="display: none;">                                        <!-- [MODIFIED] Custom Prompt for glossary is now here, available for both "same" and "custom" modes -->                                        <div class="mb-3">                                            <label for="glossary_agent_custom_prompt" class="form-label"                                                   data-i18n="glossaryCustomPromptLabel">自定义Prompt</label>                                            <textarea class="form-control" id="glossary_agent_custom_prompt"                                                      name="glossary_agent_custom_prompt" rows="3"                                                      data-i18n-placeholder="glossaryCustomPromptPlaceholder"                                                      placeholder="术语表生成提示词"></textarea>                                        </div>                                        <div class="mb-3">                                            <label class="form-label"                                                   data-i18n="glossaryGenConfigLabel">生成术语表配置</label>                                            <div class="btn-group w-100" role="group">                                                <input type="radio" class="btn-check"                                                       name="glossary_agent_config_choice" id="glossaryAgentSame"                                                       value="same" autocomplete="off" checked>                                                <label class="btn btn-outline-primary" for="glossaryAgentSame"                                                       data-i18n="glossaryGenConfigSame">与翻译配置相同</label>                                                <input type="radio" class="btn-check"                                                       name="glossary_agent_config_choice" id="glossaryAgentCustom"                                                       value="custom" autocomplete="off">                                                <label class="btn btn-outline-primary" for="glossaryAgentCustom"                                                       data-i18n="glossaryGenConfigCustom">自定义</label>                                            </div>                                        </div>                                        <!-- Custom Agent Config Form (conditionally displayed) -->                                        <div id="glossaryAgentCustomConfigContainer" class="border p-3 rounded"                                             style="display: none;">                                            <div>                                                <label for="glossary_agent_platform_select" class="form-label"                                                       data-i18n="platformLabel">选择平台</label>                                                <select class="form-select" id="glossary_agent_platform_select">                                                    <!-- Options will be copied from main select -->                                                </select>                                            </div>                                            <div class="form-text mb-3" id="glossary_base_url_display_container">                                                Base URL: <code id="glossary_base_url_display"></code>                                            </div>                                            <div class="mb-3" id="glossaryAgentBaseUrlGroup" style="display: none;">                                                <label for="glossary_agent_baseurl" class="form-label"                                                       data-i18n="baseUrlLabel">API 地址 (Base URL)</label>                                                <input type="url" class="form-control" id="glossary_agent_baseurl"                                                       name="glossary_agent_baseurl"                                                       data-i18n-placeholder="baseUrlPlaceholder"                                                       placeholder="OpenAi兼容地址">                                            </div>                                            <div class="mb-3">                                                <label for="glossary_agent_key" class="form-label">API Key</label>                                                <div class="input-group">                                                    <input type="password" class="form-control" id="glossary_agent_key"                                                           name="glossary_agent_key"                                                           data-i18n-placeholder="apiKeyPlaceholder"                                                           placeholder="请输入您的API Key">                                                    <button class="btn btn-outline-secondary toggle-password"                                                            type="button" data-target="glossary_agent_key"><i                                                            class="bi bi-eye-slash"></i></button>                                                </div>                                            </div>                                            <div class="mb-3">                                                <label for="glossary_agent_model_id" class="form-label"                                                       data-i18n="modelIdLabel">模型ID</label>                                                <input type="text" class="form-control" id="glossary_agent_model_id"                                                       name="glossary_agent_model_id"                                                       data-i18n-placeholder="modelIdPlaceholder"                                                       placeholder="例如: gpt-4-turbo, glm-4">                                            </div>                                            <!-- START: Added Target Language for Glossary Agent -->                                            <div class="mb-3">                                                <label for="glossary_agent_to_lang" class="form-label"                                                       data-i18n="targetLanguageLabel">目标语言</label>                                                <select class="form-select" id="glossary_agent_to_lang"                                                        name="glossary_agent_to_lang">                                                    <option value="Simplified Chinese">中文(简体中文)</option>                                                    <option value="English">英文(English)</option>                                                    <option value="Spanish">西班牙文(Español)</option>                                                    <option value="French">法文(Français)</option>                                                    <option value="German">德文(Deutsch)</option>                                                    <option value="Japanese">日文(日本語)</option>                                                    <option value="Korean">韩文(한국어)</option>                                                    <option value="Russian">俄文(Русский)</option>                                                    <option value="Portuguese">葡萄牙文(Português)</option>                                                    <option value="Arabic">阿拉伯文(العَرَبِيَّة)</option>                                                    <option value="Vietnamese">越南文(tiếng Việt)</option>                                                    <option value="custom" data-i18n="targetLanguageCustom">其它                                                        (自定义)                                                    </option>                                                </select>                                                <div class="mt-2" id="glossaryAgentCustomLangGroup"                                                     style="display: none;">                                                    <input type="text" class="form-control"                                                           id="glossary_agent_custom_to_lang"                                                           name="glossary_agent_custom_to_lang"                                                           data-i18n-placeholder="customLangPlaceholder"                                                           placeholder="请输入目标语言, 例如: Italian">                                                </div>                                            </div>                                            <!-- END: Added Target Language for Glossary Agent -->                                            <div class="mb-3">                                                <label for="glossary-agent-chunk-size-slider"                                                       class="form-label d-flex justify-content-between">                                                    <span><span data-i18n="chunkSizeLabel">分块大小</span>: <span                                                            id="glossary-agent-chunk-size-display"></span></span>                                                    <button type="button"                                                            class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn"                                                            id="glossary-agent-chunk-size-reset" data-i18n="resetBtn">重置                                                    </button>                                                </label>                                                <input type="range" class="form-range" min="1000" max="8000" step="100"                                                       id="glossary-agent-chunk-size-slider"                                                       name="glossary_agent_chunk_size">                                            </div>                                            <div class="mb-3">                                                <label for="glossary-agent-concurrent-slider"                                                       class="form-label d-flex justify-content-between">                                                    <span><span data-i18n="concurrentLabel">并发数</span>: <span                                                            id="glossary-agent-concurrent-display"></span></span>                                                    <button type="button"                                                            class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn"                                                            id="glossary-agent-concurrent-reset" data-i18n="resetBtn">重置                                                    </button>                                                </label>                                                <input type="range" class="form-range" min="1" max="60" step="1"                                                       id="glossary-agent-concurrent-slider"                                                       name="glossary_agent_concurrent">                                            </div>                                            <div class="mb-3">                                                <label for="glossary-agent-temperature-slider"                                                       class="form-label d-flex justify-content-between">                                                    <span>Temperature: <span                                                            id="glossary-agent-temperature-display">0.7</span></span>                                                    <button type="button"                                                            class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn"                                                            id="glossary-agent-temperature-reset" data-i18n="resetBtn">                                                        重置                                                    </button>                                                </label>                                                <input type="range" class="form-range" min="0" max="2" step="0.1"                                                       id="glossary-agent-temperature-slider"                                                       name="glossary_agent_temperature">                                            </div>                                            <div class="mb-3">                                                <label for="glossary-agent-retry-slider"                                                       class="form-label d-flex justify-content-between">                                                    <span><span data-i18n="retryLabel">重试次数</span>: <span                                                            id="glossary-agent-retry-display"></span></span>                                                    <button type="button"                                                            class="btn btn-sm btn-outline-secondary py-0 px-1 slider-reset-btn"                                                            id="glossary-agent-retry-reset" data-i18n="resetBtn">重置                                                    </button>                                                </label>                                                <input type="range" class="form-range" min="1" max="6" step="1"                                                       id="glossary-agent-retry-slider" name="glossary_agent_retry">                                            </div>                                            <div class="mb-3">                                                <label class="form-label" data-i18n="thinkingModeLabel">思考模式</label>                                                <div id="glossaryAgentThinkingModeBtnGroup" class="btn-group w-100"                                                     role="group">                                                    <input type="radio" class="btn-check" name="glossary_agent_thinking"                                                           id="glossaryAgentThinkingEnable" value="enable"                                                           autocomplete="off">                                                    <label class="btn btn-outline-primary"                                                           for="glossaryAgentThinkingEnable"                                                           data-i18n="thinkingModeEnable">启用</label>                                                    <input type="radio" class="btn-check" name="glossary_agent_thinking"                                                           id="glossaryAgentThinkingDisable" value="disable"                                                           autocomplete="off">                                                    <label class="btn btn-outline-primary"                                                           for="glossaryAgentThinkingDisable"                                                           data-i18n="thinkingModeDisable">禁用(推荐)</label>                                                    <input type="radio" class="btn-check" name="glossary_agent_thinking"                                                           id="glossaryAgentThinkingDefault" value="default"                                                           autocomplete="off" checked>                                                    <label class="btn btn-outline-primary"                                                           for="glossaryAgentThinkingDefault"                                                           data-i18n="thinkingModeDefault">默认</label>                                                </div>                                            </div>                                            <!-- START: ADDED GLOSSARY AGENT SYSTEM PROXY SWITCH -->                                            <div class="form-check form-switch mb-3">                                                <input class="form-check-input" type="checkbox" role="switch"                                                       id="glossary_agent_system_proxy_enable"                                                       name="glossary_agent_system_proxy_enable">                                                <label class="form-check-label" for="glossary_agent_system_proxy_enable"                                                       data-i18n="systemProxyLabel">启用系统代理</label>                                            </div>                                            <!-- END: ADDED GLOSSARY AGENT SYSTEM PROXY SWITCH -->                                        </div>                                    </div>                                </div>                            </div>                        </div>                    </div>                </form>                <!-- [NEW] Config Import/Export Buttons -->                <div class="d-flex justify-content-center gap-2 mt-4">                    <button type="button" class="btn btn-outline-primary" id="importConfigBtn">                        <i class="bi bi-box-arrow-in-down me-1"></i><span data-i18n="importConfigBtn">导入配置</span>                    </button>                    <button type="button" class="btn btn-outline-secondary" id="exportConfigBtn">                        <i class="bi bi-box-arrow-up me-1"></i><span data-i18n="exportConfigBtn">导出配置</span>                    </button>                </div>                <input type="file" id="configFileInput" class="d-none" accept=".json">                <!-- 项目信息 -->                <div class="mt-4 text-center text-muted small project-info">                    <p class="bi bi-github mb-2" data-i18n="githubInfo">                        GitHub主页(欢迎star❤): <br/>                        <a href="https://github.com/xunbu/docutranslate" target="_blank" rel="noopener noreferrer">https://github.com/xunbu/docutranslate</a>                    </p>                    <p class="bi bi-tencent-qq mb-2" data-i18n="qqGroupInfo">                        交流QQ群: 1047781902                    </p>                    <p class="bi mb-0">version:<span id="versionDisplay"></span></p>                </div>            </div>        </div>        <!-- Right: Task Area -->        <div class="col-lg-8">            <div class="task-area" id="task-area-container">                <div class="d-flex justify-content-between align-items-center mb-3">                    <h4 class="mb-0"><i class="bi bi-list-task me-2"></i><span data-i18n="taskListTitle">任务列表</span>                    </h4>                    <button class="btn btn-primary" id="addNewTaskBtn"><i class="bi bi-plus-circle-fill me-2"></i><span                            data-i18n="newTaskBtn">新建任务</span>                    </button>                </div>                <div id="task-container">                    <!-- Task cards will be injected here -->                </div>                <div id="no-task-placeholder" class="text-center text-muted mt-5">                    <img src="/static/favicon.ico" alt="LOGO" style="width:10%;min-width: 55px; height: auto;">                    <p class="mt-3" data-i18n="noTaskPlaceholder">当前没有任务，点击“新建任务”开始吧！</p>                </div>            </div>        </div>    </div></div><!-- Task Card Template --><template id="taskCardTemplate">    <div class="card mb-3 task-card">        <div class="card-header d-flex justify-content-between align-items-center">            <span class="fw-bold"><span data-i18n="taskCardIdLabel">任务 ID</span>: <code class="task-id-display"><span                    class="task-id-placeholder" data-i18n="taskCardIdPlaceholder">等待提交...</span></code></span>            <button type="button" class="btn-close remove-task-btn" aria-label="Close"></button>        </div>        <div class="card-body">            <div class="row">                <div class="col-md-5">                    <input type="file" class="d-none file-input">                    <div class="file-drop-area">                        <div class="file-drop-default">                            <i class="bi bi-cloud-arrow-up fs-1"></i>                            <p class="file-drop-prompt mb-0" data-i18n="taskCardFileDrop">点击或拖拽文件到此处</p>                        </div>                        <div class="file-drop-selected" style="display: none;">                            <i class="bi bi-check-circle-fill fs-1 text-success"></i>                            <p class="mb-0 mt-2 fw-bold text-success" data-i18n="taskCardFileSelected">文件已选择</p>                        </div>                    </div>                    <div class="file-name-display-wrapper mt-2" style="display: none;">                        <span class="fw-bold" data-i18n="taskCardFilenameLabel">文件名: </span>                        <span class="file-name-display text-success"></span>                    </div>                </div>                <div class="col-md-7">                    <h6><i class="bi bi-terminal me-2"></i><span data-i18n="taskCardLogLabel">日志</span></h6>                    <div class="log-area-wrapper">                        <div class="log-area"></div>                        <button type="button" class="btn btn-sm btn-outline-secondary copy-log-btn"                                data-bs-toggle="tooltip" data-bs-placement="top" data-i18n-title="copyLogsTooltip">                            <i class="bi bi-clipboard"></i>                        </button>                    </div>                    <div class="mt-2">                        <div class="status-message-container">                            <span class="status-message small text-muted"                                  data-i18n="taskCardStatusWaiting">等待上传文件...</span>                        </div>                    </div>                </div>            </div>        </div>        <div class="card-footer d-flex justify-content-between align-items-center">            <div class="download-buttons" style="display: none;">                <button class="btn btn-sm btn-success preview-html-btn" style="display: none;"><i                        class="bi bi-eye-fill me-1"></i><span data-i18n="taskCardPreviewBtn">预览</span>                </button>                <div class="btn-group">                    <button type="button" class="btn btn-sm btn-secondary dropdown-toggle" data-bs-toggle="dropdown"                            aria-expanded="false">                        <i class="bi bi-download me-1"></i><span data-i18n="taskCardDownloadBtn">下载</span>                    </button>                    <ul class="dropdown-menu download-menu-container">                        <!-- Populated by JavaScript -->                    </ul>                </div>                <!-- [NEW] Attachment Button Group -->                <div class="btn-group attachment-btn-group" style="display: none;">                    <button type="button" class="btn btn-sm btn-info dropdown-toggle" data-bs-toggle="dropdown"                            aria-expanded="false">                        <i class="bi bi-paperclip me-1"></i><span data-i18n="taskCardAttachmentBtn">附件</span>                    </button>                    <ul class="dropdown-menu attachment-menu-container">                        <!-- Populated by JavaScript -->                    </ul>                </div>            </div>            <button class="btn btn-primary start-translate-btn ms-auto"><i class="bi bi-play-fill me-1"></i><span                    data-i18n="taskCardStartBtn">开始翻译</span>            </button>        </div>    </div></template><!-- Reusable Download Menu Template --><template id="downloadMenuTemplate">    <li class="download-item-md"><a class="dropdown-item" href="#"><i            class="bi bi-markdown me-2"></i><span data-i18n="downloadMdEmbedded">Markdown(嵌图)</span></a></li>    <li class="download-item-md-zip"><a class="dropdown-item" href="#"><i            class="bi bi-file-zip me-2"></i><span data-i18n="downloadMdZip">Markdown压缩包</span></a></li>    <li class="download-item-txt"><a class="dropdown-item" href="#"><i            class="bi bi-filetype-txt me-2"></i>TXT</a></li>    <li class="download-item-json"><a class="dropdown-item" href="#"><i            class="bi bi-filetype-json me-2"></i>JSON</a></li>    <li class="download-item-docx"><a class="dropdown-item" href="#"><i            class="bi bi-filetype-docx me-2"></i>DOCX</a></li>    <li class="download-item-xlsx"><a class="dropdown-item" href="#"><i            class="bi bi-filetype-xlsx me-2"></i>XLSX</a></li>    <li class="download-item-csv"><a class="dropdown-item" href="#"><i            class="bi bi-filetype-csv me-2"></i>CSV</a></li>    <li class="download-item-srt"><a class="dropdown-item" href="#"><i            class="bi bi-file-text me-2"></i>SRT</a></li>    <li class="download-item-epub"><a class="dropdown-item" href="#"><i            class="bi bi-book me-2"></i>EPUB</a></li>    <li class="download-item-ass"><a class="dropdown-item" href="#"><i            class="bi bi-file-easel me-2"></i><span data-i18n="downloadAss">ASS</span></a></li>    <li class="download-item-html"><a class="dropdown-item" href="#"><i            class="bi bi-filetype-html me-2"></i>HTML</a></li>    <li class="download-item-pdf"><a class="dropdown-item" href="#"><i            class="bi bi-file-earmark-pdf me-2"></i>PDF</a></li></template><!-- Preview Offcanvas with Resizable Panes --><div class="offcanvas offcanvas-end" data-bs-scroll="true" tabindex="-1" id="previewOffcanvas"     aria-labelledby="previewOffcanvasLabel">    <div class="offcanvas-header border-bottom">        <h5 class="offcanvas-title" id="previewOffcanvasLabel" data-i18n="previewTitle">预览</h5>        <div class="btn-group me-auto ms-4" role="group">            <button type="button" class="btn btn-sm btn-primary" id="setBilingualViewBtn"                    data-i18n="previewBilingualBtn">双语            </button>            <button type="button" class="btn btn-sm btn-outline-primary" id="setTranslatedOnlyViewBtn"                    data-i18n="previewTranslatedOnlyBtn">仅译文            </button>        </div>        <button type="button" class="btn btn-sm btn-outline-secondary ms-2" id="syncScrollBtn" data-bs-toggle="tooltip" data-bs-placement="bottom" data-i18n-title="syncScrollTooltip" title="联动滑动">            <i class="bi bi-link-45deg"></i>        </button>        <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>    </div>    <div class="offcanvas-body d-flex flex-column p-2">        <div class="preview-split-container flex-grow-1">            <div id="originalPreviewContainer" class="preview-pane-wrapper">                <h6 class="text-center text-muted small" data-i18n="previewOriginal">原文</h6>                <div class="preview-pane" id="originalPreviewPane"></div>            </div>            <div id="translatedPreviewContainer" class="preview-pane-wrapper">                <h6 class="text-center text-muted small" data-i18n="previewTranslated">译文</h6>                <div class="preview-pane">                    <iframe id="translatedPreviewFrame" src="about:blank"></iframe>                </div>            </div>        </div>        <div class="offcanvas-footer mt-2 pt-3 border-top d-flex justify-content-end align-items-center">            <button type="button" class="btn btn-secondary" data-bs-dismiss="offcanvas" data-i18n="closeBtn">关闭            </button>            <div class="btn-group ms-2">                <button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown"                        aria-expanded="false" id="previewDownloadBtn">                    <i class="bi bi-download me-1"></i><span data-i18n="downloadBtn">下载</span>                </button>                <ul class="dropdown-menu dropdown-menu-end" id="previewDownloadMenu">                    <!-- Populated by JavaScript -->                </ul>            </div>        </div>    </div></div><!-- Hidden iframe for direct PDF printing --><iframe id="printFrame" style="display: none;"></iframe><!-- Bottom Left Controls --><div class="bottom-left-controls">    <!-- Language Switcher -->    <div class="dropdown">        <button class="btn btn-secondary dropdown-toggle" type="button" id="language-switcher" data-bs-toggle="dropdown"                aria-expanded="false" title="切换语言 / Switch Language">            <i class="bi bi-translate"></i>        </button>        <ul class="dropdown-menu" aria-labelledby="language-switcher" id="languageMenu">            <li><a class="dropdown-item" href="#" data-lang="zh">中文</a></li>            <li><a class="dropdown-item" href="#" data-lang="en">English</a></li>        </ul>    </div>    <!-- Theme Switcher -->    <div class="dropdown">        <button class="btn btn-secondary dropdown-toggle" type="button" id="theme-switcher" data-bs-toggle="dropdown"                aria-expanded="false">            <i class="bi bi-circle-half"></i>        </button>        <ul class="dropdown-menu" aria-labelledby="theme-switcher">            <li>                <button class="dropdown-item" type="button" data-bs-theme-value="light"><i                        class="bi bi-sun-fill me-2"></i>                    Light                </button>            </li>            <li>                <button class="dropdown-item" type="button" data-bs-theme-value="dark"><i                        class="bi bi-moon-stars-fill me-2"></i> Dark                </button>            </li>            <li>                <button class="dropdown-item active" type="button" data-bs-theme-value="auto"><i                        class="bi bi-circle-half me-2"></i> Auto                </button>            </li>        </ul>    </div></div><!-- Tutorial Modal --><!-- Tutorial Modal --><div class="modal fade" id="tutorialModal" tabindex="-1" aria-labelledby="tutorialModalLabel" aria-hidden="true">    <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">        <div class="modal-content">            <div class="modal-header">                <h5 class="modal-title" id="tutorialModalLabel"><i class="bi bi-book-half me-2"></i><span                        data-i18n="tutorialModalTitle">使用教程</span></h5>                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>            </div>            <div class="modal-body" data-i18n="tutorialModalBody">                <p><i class="bi bi-camera-video me-2"></i>视频教程可以在B站搜索 <a                        href="https://search.bilibili.com/all?keyword=docutranslate" target="_blank">docutranslate</a>                    获取。</p>                <p>欢迎使用 DocuTranslate！请按照以下步骤完成文档翻译：</p>                <ol>                    <li>                        <strong><i class="bi bi-diagram-3 me-2"></i>第一步：选择工作流</strong>                        <p class="mt-2">                            在左侧配置面板的顶部，首先选择最适合您文件类型的处理流程。                        <div class="alert alert-info mt-2" role="alert">                            <i class="bi bi-lightbulb-fill me-2"></i>提示:                            默认已开启“自动选择工作流”。您只需上传文件，系统会自动为您匹配合适的工作流，简化操作。                        </div>                        </p>                        <ul>                            <li><b>转Markdown再翻译</b>: 适用于翻译PDF、markdown、图片等文件。这是最通用和强大的模式。</li>                            <li><b>纯文本翻译</b>: 用于翻译 <code>.txt</code> 纯文本文件。</li>                            <li><b>EPUB翻译</b>: 用于翻译 <code>.epub</code> 电子书文件。</li>                            <li><b>DOCX翻译</b>: 用于翻译 <code>.docx</code> Word文档。</li>                            <li><b>XLSX翻译</b>: 用于翻译 <code>.xlsx</code> 或 <code>.csv</code> 电子表格文件。</li>                            <li><b>SRT字幕翻译</b>: 用于翻译 <code>.srt</code> 字幕文件。</li>                            <li><b>ASS字幕翻译</b>: 用于翻译 <code>.ass</code> 特效字幕文件。</li>                            <li><b>JSON翻译</b>: 用于翻译 <code>.json</code> 文件中的特定字段。</li>                            <li><b>HTML翻译</b>: 用于翻译 <code>.html</code> 网页文件。</li>                        </ul>                    </li>                    <li>                        <strong><i class="bi bi-gear-fill me-2"></i>第二步：配置参数</strong>                        <p class="mt-2">                            选择工作流后，下方会显示相关的配置选项。请依次完成设置（所有配置都会自动保存在您的浏览器中）：</p>                        <p class="mb-2"><strong>A. 工作流特定选项</strong> (根据您第一步的选择出现):</p>                        <ul class="ms-4">                            <li><u>如果选择“转Markdown再翻译”</u>，请配置 <strong>解析配置</strong>：                                <ul>                                    <li><strong>解析引擎</strong>:                                        选择一个引擎将您的文件（如PDF）转换为适合翻译的Markdown格式。如果您的文件已经是Markdown格式，则无需选择。                                    </li>                                    <li><strong>Mineru Token</strong>: 如果您选择 <code>minerU</code> 引擎，需要在此处填入您的Token。                                    </li>                                </ul>                            </li>                            <li><u>如果选择“纯文本/DOCX/XLSX/SRT/ASS/EPUB/HTML”</u>，请配置其 <strong>翻译选项</strong>：                                <ul>                                    <li><strong>插入模式</strong>: 定义翻译结果如何放入文档。您可以选择直接“替换”原文，或是在原文之后“附加”，或是在原文之前“前置”。                                    </li>                                    <li><strong>分隔符</strong>: 当选择“附加”或“前置”模式时，此项用于在原文和译文之间插入分隔符（例如ASS格式常用 <code>\N</code>，EPUB格式常用 <code>&lt;br /&gt;</code> 作为换行分隔符）。                                    </li>                                </ul>                            </li>                            <li><u>如果选择“JSON翻译”</u>，请配置 <strong>JSON路径</strong>：                                <ul>                                    <li><strong>需要翻译的JSON路径</strong>: 每行输入一个 <a                                            href="https://goessner.net/articles/JsonPath/" target="_blank">JSONPath</a>                                        表达式，将翻译所有与路径匹配的对象中的字符串。例如:<code>$.*</code>(翻译全部字符串)，<code>$..description</code>(翻译所有键为<code>description</code>的值)。                                    </li>                                </ul>                            </li>                        </ul>                        <p class="mb-2 mt-3"><strong>B. 通用选项</strong> (适用于所有工作流):</p>                        <ul class="ms-4">                            <li><strong>翻译模型</strong>:                                <ul>                                    <li><strong>选择平台/API 地址/API Key/模型ID</strong>: 配置您希望使用的AI翻译服务。模型能力指令遵循越强，<strong>出错</strong>、<strong>漏翻</strong>的概率越低。                                    </li>                                    <li><strong>跳过翻译</strong>: 勾选此项后，将只执行文档解析和格式转换，不调用AI进行翻译。                                    </li>                                </ul>                            </li>                            <li><strong>翻译配置</strong>:                                <ul>                                    <li><strong>目标语言</strong>: 指定翻译的目标语言。</li>                                    <li><strong>自定义Prompt</strong>: 可选，添加额外指令，如“人名保持原文不翻译”。</li>                                    <li><strong>思考模式</strong>: 针对部分支持混合推理的模型进行设置，建议选择“禁用(推荐)”。                                    </li>                                    <li><strong>分块大小/并发数等</strong>: 高级参数，用于调整性能和API请求行为，通常保持默认即可。                                    </li>                                </ul>                            </li>                            <li><strong>术语表</strong>:                                <ul>                                    <li><strong>上传术语表 (可选)</strong>: 上传CSV文件（需包含'src'和'dst'列）来保证特定术语翻译的统一性和准确性。                                    </li>                                    <li><strong>自动生成术语表</strong>: 启用后，程序会先从原文中提取术语并生成一个术语表，然后再进行翻译。                                    </li>                                </ul>                            </li>                        </ul>                    </li>                    <li>                        <strong><i class="bi bi-file-earmark-arrow-up-fill me-2"></i>第三步：上传文件</strong>                        <p class="mt-2">在右侧的任务列表中，点击或拖拽您的文档到文件上传区域。</p>                    </li>                    <li>                        <strong><i class="bi bi-play-circle-fill me-2"></i>第四步：开始翻译</strong>                        <p class="mt-2">文件选择成功后，点击任务卡片右下角的 <span                                class="badge bg-primary">开始翻译</span> 按钮。系统将开始处理任务，您可以在日志区域查看实时进度。                        </p>                    </li>                    <li>                        <strong><i class="bi bi-check-circle-fill me-2"></i>第五步：查看与下载</strong>                        <p class="mt-2">翻译完成后，任务卡片下方会出现操作按钮：</p>                        <ul>                            <li><span class="badge bg-success"><i class="bi bi-eye-fill me-1"></i>预览</span>:                                在右侧滑出的面板中进行原文和译文的对照预览。                            </li>                            <li><span class="badge bg-secondary"><i class="bi bi-download me-1"></i>下载</span>:                                下载包括 PDF, DOCX, Markdown 等多种格式的译文。                            </li>                            <li><span class="badge bg-info"><i class="bi bi-paperclip me-1"></i>附件</span>:                                如果翻译过程中生成了附加文件（如自动生成的术语表），可在此处下载。                            </li>                        </ul>                    </li>                </ol>                <div class="alert alert-warning mt-3" role="alert">                    <i class="bi bi-info-circle-fill me-2"></i><strong>重要提示</strong>:                    所有配置都会自动保存在您的浏览器本地，方便下次使用。您也可以使用新增的“导出配置”和“导入配置”按钮来备份和恢复您的设置。                </div>            </div>            <div class="modal-footer">                <button type="button" class="btn btn-primary" data-bs-dismiss="modal" data-i18n="tutorialUnderstandBtn">                    我明白了                </button>            </div>        </div>    </div></div><!-- Contributors Modal --><div class="modal fade" id="contributorsModal" tabindex="-1" aria-labelledby="contributorsModalLabel"     aria-hidden="true">    <div class="modal-dialog modal-dialog-centered">        <div class="modal-content">            <div class="modal-header">                <h5 class="modal-title" id="contributorsModalLabel"><i                        class="bi bi-heart-fill me-2 text-danger"></i><span                        data-i18n="contributorsModalTitle">感谢贡献</span>                </h5>                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>            </div>            <div class="modal-body">                <p data-i18n="contributorsPara1">DocuTranslate是一个开源项目！大家的需求与使用是项目进步的动力。</p>                <p data-i18n="contributorsPara2">感谢所有资助项目、提交代码与宝贵建议及给项目star的朋友们！</p>                <div class="alert alert-success mt-4" role="alert">                    <p data-i18n="contributorsWelcome">欢迎通过以下方式参与贡献：</p>                    <hr>                    <p class="mb-0">                        <a href="https://github.com/xunbu/docutranslate" target="_blank"                           class="btn btn-info btn-sm ms-2">                            <i class="bi bi-github me-1"></i><span data-i18n="contributorsGithub">github 主页</span>                        </a>                        <a href="https://github.com/xunbu/docutranslate/pulls" target="_blank"                           class="btn btn-success btn-sm ms-2">                            <i class="bi bi-git me-1"></i><span data-i18n="contributorsPR">提交 Pull Request</span>                        </a>                        <a href="https://github.com/xunbu/docutranslate/issues" target="_blank"                           class="btn btn-warning btn-sm ms-2">                            <i class="bi bi-bug-fill me-1"></i><span data-i18n="contributorsIssue">报告 Issue</span>                        </a>                    </p>                    <hr>                    <p data-i18n="contributorsQQ">或者通过QQ群联系作者：<span>1047781902</span></p>                </div>            </div>            <div class="modal-footer">                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="closeBtn">关闭                </button>            </div>        </div>    </div></div><!-- [NEW] Glossary Modal --><div class="modal fade" id="glossaryModal" tabindex="-1" aria-labelledby="glossaryModalLabel" aria-hidden="true">    <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">        <div class="modal-content">            <div class="modal-header">                <h5 class="modal-title" id="glossaryModalLabel" data-i18n="glossaryModalTitle">当前术语表</h5>                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>            </div>            <div class="modal-body">                <table class="table table-striped table-hover">                    <thead>                    <tr>                        <th scope="col" data-i18n="glossaryTableSource">原文 (src)</th>                        <th scope="col" data-i18n="glossaryTableDestination">译文 (dst)</th>                    </tr>                    </thead>                    <tbody id="glossaryTableBody">                    <!-- Content will be populated by JS -->                    </tbody>                </table>            </div>            <div class="modal-footer">                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="closeBtn">关闭                </button>            </div>        </div>    </div></div><!-- Bootstrap JS --><script src="/static/bootstrap.bundle.min.js" crossorigin="anonymous"></script><!-- Split.js for resizable panes --><script src="/static/split.min.js"></script><!-- [NEW] PapaParse for CSV parsing --><script src="/static/papaparse.min.js"></script><script type="module">    // --- I18N Data ---    // Initialize with a minimal fallback for critical error messages.    // The full dataset will be loaded asynchronously in the init() function.    let i18nData = {        zh: {            init_i18n_failed_alert: '加载界面翻译资源失败，请检查网络连接或联系管理员。',            init_failed_alert: '初始化失败，无法连接到后端服务。请检查服务是否运行或刷新页面。',            copyLogsTooltip: '复制日志',            syncScrollTooltip: '联动滑动',        },        en: {            init_i18n_failed_alert: 'Failed to load interface translations. Please check your network connection or contact an administrator.',            init_failed_alert: 'Initialization failed, could not connect to the backend service. Please ensure the service is running and refresh the page.',            copyLogsTooltip: 'Copy logs',            syncScrollTooltip: 'Sync Scrolling',        }    };    let currentLang = 'zh'; // Global language state    // --- I18N Helper Functions ---    const getText = (key, fallback = '') => {        const translations = i18nData[currentLang] || i18nData.zh;        return translations[key] || fallback || key;    };    /**     * Updates the UI language based on the selected language.     * @param {string} lang - The language code (e.g., 'zh', 'en').     */    function setLanguage(lang) {        if (!i18nData[lang]) return;        currentLang = lang;        const translations = i18nData[lang];        document.documentElement.lang = lang === 'zh' ? 'zh-CN' : 'en';        // Define attributes and corresponding properties to update        const i18nTargets = {            'data-i18n': (el, text) => el.innerHTML = text,            'data-i18n-placeholder': (el, text) => el.placeholder = text,            'data-i18n-title': (el, text) => {                if (el.tagName === 'TITLE') {                    el.textContent = text;                } else {                    const tooltipInstance = bootstrap.Tooltip.getInstance(el);                    if (tooltipInstance) {                        tooltipInstance.setContent({'.tooltip-inner': text});                    } else {                        // 如果某个元素不是 Bootstrap tooltip 但也用了这个属性，可以保留设置 title 的逻辑作为备用                        el.title = text;                    }                }            }        };        // Update all elements with i18n attributes in a single pass        Object.entries(i18nTargets).forEach(([attribute, updater]) => {            document.querySelectorAll(`[${attribute}]`).forEach(el => {                const key = el.getAttribute(attribute);                if (translations[key] !== undefined) {                    updater(el, translations[key]);                }            });        });        // Update dynamic UI parts that are built with JS        // --- MODIFIED HERE ---        // This function now correctly re-applies numbering after text has been translated.        updateWorkflowUI();        updateConvertEnginUI(true);        mainPlatformUpdater(); // 新增: 重新运行平台UI更新器以应用翻译        // Update language switcher active state        const langMenu = document.getElementById('languageMenu');        langMenu.querySelectorAll('a.active').forEach(a => a.classList.remove('active'));        const activeLink = langMenu.querySelector(`a[data-lang="${lang}"]`);        if (activeLink) activeLink.classList.add('active');        // Save preference        saveToStorage('ui_language', lang);    }    function initI18n() {        const langMenu = document.getElementById('languageMenu');        langMenu.querySelectorAll('a[data-lang]').forEach(a => {            a.addEventListener('click', (e) => {                e.preventDefault();                setLanguage(a.dataset.lang);            });        });        const savedLang = getFromStorage('ui_language') || (navigator.language.toLowerCase().startsWith('en') ? 'en' : 'zh');        setLanguage(savedLang);    }    // --- End I18N ---    const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');    const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));    // --- DOM Elements ---    const settingsForm = document.getElementById('translateForm');    // Workflow elements    const workflowTypeSelect = document.getElementById('workflowTypeSelect');    const autoWorkflowSwitch = document.getElementById('autoWorkflowSwitch');    const parsingSettingsContainer = document.getElementById('parsingSettingsContainer');    const parsingSettingsTitle = document.getElementById('parsingSettingsTitle');    const jsonSettingsContainer = document.getElementById('jsonSettingsContainer');    const jsonSettingsTitle = document.getElementById('jsonSettingsTitle');    const jsonPathsTextarea = document.getElementById('json_paths_textarea');    const txtSettingsContainer = document.getElementById('txtSettingsContainer');    const txtSettingsTitle = document.getElementById('txtSettingsTitle');    const txtInsertModeSelect = document.getElementById('txt_insert_mode');    const txtSeparatorGroup = document.getElementById('txtSeparatorGroup');    const txtSeparatorInput = document.getElementById('txt_separator');    const xlsxSettingsContainer = document.getElementById('xlsxSettingsContainer');    const xlsxSettingsTitle = document.getElementById('xlsxSettingsTitle');    const xlsxInsertModeSelect = document.getElementById('xlsx_insert_mode');    const xlsxSeparatorGroup = document.getElementById('xlsxSeparatorGroup');    const xlsxSeparatorInput = document.getElementById('xlsx_separator');    const xlsxTranslateRegionsTextarea = document.getElementById('xlsx_translate_regions');    const docxSettingsContainer = document.getElementById('docxSettingsContainer');    const docxSettingsTitle = document.getElementById('docxSettingsTitle');    const docxInsertModeSelect = document.getElementById('docx_insert_mode');    const docxSeparatorGroup = document.getElementById('docxSeparatorGroup');    const docxSeparatorInput = document.getElementById('docx_separator');    const srtSettingsContainer = document.getElementById('srtSettingsContainer');    const srtSettingsTitle = document.getElementById('srtSettingsTitle');    const srtInsertModeSelect = document.getElementById('srt_insert_mode');    const srtSeparatorGroup = document.getElementById('srtSeparatorGroup');    const srtSeparatorInput = document.getElementById('srt_separator');    const epubSettingsContainer = document.getElementById('epubSettingsContainer');    const epubSettingsTitle = document.getElementById('epubSettingsTitle');    const epubInsertModeSelect = document.getElementById('epub_insert_mode');    const epubSeparatorGroup = document.getElementById('epubSeparatorGroup');    const epubSeparatorInput = document.getElementById('epub_separator');    const htmlSettingsContainer = document.getElementById('htmlSettingsContainer');    const htmlSettingsTitle = document.getElementById('htmlSettingsTitle');    const htmlInsertModeSelect = document.getElementById('html_insert_mode');    const htmlSeparatorGroup = document.getElementById('htmlSeparatorGroup');    const htmlSeparatorInput = document.getElementById('html_separator');    const assSettingsContainer = document.getElementById('assSettingsContainer');    const assSettingsTitle = document.getElementById('assSettingsTitle');    const assInsertModeSelect = document.getElementById('ass_insert_mode');    const assSeparatorGroup = document.getElementById('assSeparatorGroup');    const assSeparatorInput = document.getElementById('ass_separator');    const aiSettingsTitle = document.getElementById('aiSettingsTitle');    const translationSettingsTitle = document.getElementById('translationSettingsTitle');    const glossaryGenSettingsTitle = document.getElementById('glossaryGenSettingsTitle');    // Parsing elements    const convertEnginSelect = document.getElementById('convert_engine');    const mineruTokenGroup = document.getElementById('mineruTokenGroup');    const mineruTokenInput = document.getElementById('mineru_token');    const mineruModelVersionGroup = document.getElementById('mineruModelVersionGroup');    const modelVersionSelect = document.getElementById('model_version');    const formulaCheckbox = document.getElementById('formula_ocr');    const codeCheckbox = document.getElementById('code_ocr');    const formulaOcrSwitchContainer = document.getElementById('formulaOcrSwitchContainer');    const codeOcrSwitchContainer = document.getElementById('codeOcrSwitchContainer');    // NEW Mineru Deploy Elements    const mineruDeployGroup = document.getElementById('mineruDeployGroup');    const mineruDeployBaseUrlInput = document.getElementById('mineru_deploy_base_url');    const mineruDeployBackendSelect = document.getElementById('mineru_deploy_backend');    const mineruDeployStartPageInput = document.getElementById('mineru_deploy_start_page_id');    const mineruDeployEndPageInput = document.getElementById('mineru_deploy_end_page_id');    const mineruDeployFormulaEnableSwitch = document.getElementById('mineru_deploy_formula_enable');    // AI elements    const skipTranslationSwitch = document.getElementById('skipTranslationSwitch');    const aiModelSettingsContainer = document.getElementById('aiModelSettingsContainer');    const platformSelect = document.getElementById('platform_select');    const apiHref = document.getElementById('api_href');    const apiHrefInfo = document.getElementById('api_href_info');    const baseUrlGroup = document.getElementById('baseUrlGroup');    const baseUrlInput = document.getElementById('base_url');    const apikeyInput = document.getElementById('api_key');    const modelInput = document.getElementById('model_id');    const mainBaseUrlDisplay = document.getElementById('main_base_url_display');    const systemProxyEnableSwitch = document.getElementById('system_proxy_enable');    // Translation elements    const translationSettingsAccordionItem = document.getElementById('translationSettingsAccordionItem');    const toLangSelect = document.getElementById('to_lang');    const customLangGroup = document.getElementById('customLangGroup');    const customLangInput = document.getElementById('custom_to_lang');    const customPromptTranslateArea = document.getElementById("custom_prompt");    // [NEW] Glossary elements    const glossaryFilesInput = document.getElementById('glossary_files');    const viewGlossaryBtn = document.getElementById('viewGlossaryBtn');    const clearGlossaryBtn = document.getElementById('clearGlossaryBtn');    const glossaryModalEl = document.getElementById('glossaryModal');    const glossaryModal = new bootstrap.Modal(glossaryModalEl);    const glossaryTableBody = document.getElementById('glossaryTableBody');    // [MODIFIED] Glossary Generation elements    const glossaryGenerationContainer = document.getElementById('glossaryGenerationContainer');    const glossaryGenerateEnableSwitch = document.getElementById('glossary_generate_enable');    const glossaryAgentOptionsContainer = document.getElementById('glossaryAgentOptionsContainer');    const glossaryAgentConfigChoiceRadios = document.querySelectorAll('input[name="glossary_agent_config_choice"]');    const glossaryAgentCustomConfigContainer = document.getElementById('glossaryAgentCustomConfigContainer');    const glossaryAgentPlatformSelect = document.getElementById('glossary_agent_platform_select');    const glossaryAgentBaseUrlGroup = document.getElementById('glossaryAgentBaseUrlGroup');    const glossaryAgentBaseUrlInput = document.getElementById('glossary_agent_baseurl');    const glossaryAgentKeyInput = document.getElementById('glossary_agent_key');    const glossaryAgentModelIdInput = document.getElementById('glossary_agent_model_id');    const glossaryBaseUrlDisplay = document.getElementById('glossary_base_url_display');    const glossaryAgentToLangSelect = document.getElementById('glossary_agent_to_lang');    const glossaryAgentCustomLangGroup = document.getElementById('glossaryAgentCustomLangGroup');    const glossaryAgentCustomLangInput = document.getElementById('glossary_agent_custom_to_lang');    const glossaryAgentCustomPromptTextarea = document.getElementById('glossary_agent_custom_prompt');    const glossaryAgentTemperatureSlider = document.getElementById('glossary-agent-temperature-slider');    const glossaryAgentTemperatureDisplay = document.getElementById('glossary-agent-temperature-display');    const glossaryAgentTemperatureReset = document.getElementById('glossary-agent-temperature-reset');    const glossaryAgentChunkSizeSlider = document.getElementById('glossary-agent-chunk-size-slider');    const glossaryAgentChunkSizeDisplay = document.getElementById('glossary-agent-chunk-size-display');    const glossaryAgentChunkSizeReset = document.getElementById('glossary-agent-chunk-size-reset');    const glossaryAgentConcurrentSlider = document.getElementById('glossary-agent-concurrent-slider');    const glossaryAgentConcurrentDisplay = document.getElementById('glossary-agent-concurrent-display');    const glossaryAgentConcurrentReset = document.getElementById('glossary-agent-concurrent-reset');    const glossaryAgentRetrySlider = document.getElementById('glossary-agent-retry-slider');    const glossaryAgentRetryDisplay = document.getElementById('glossary-agent-retry-display');    const glossaryAgentRetryReset = document.getElementById('glossary-agent-retry-reset');    const glossaryAgentThinkingRadios = document.querySelectorAll('input[name="glossary_agent_thinking"]');    const glossaryAgentSystemProxyEnableSwitch = document.getElementById('glossary_agent_system_proxy_enable');    // Moved Advanced elements (now part of translation settings)    const chunkSizeSlider = document.getElementById('chunk-size-slider');    const chunkSizeDisplay = document.getElementById('chunk-size-display');    const chunkSizeReset = document.getElementById('chunk-size-reset');    const concurrentSlider = document.getElementById('concurrent-slider');    const concurrentDisplay = document.getElementById('concurrent-display');    const concurrentReset = document.getElementById("concurrent-reset");    const temperatureSlider = document.getElementById('temperature-slider');    const temperatureDisplay = document.getElementById('temperature-display');    const temperatureReset = document.getElementById("temperature-reset");    const retrySlider = document.getElementById('retry-slider');    const retryDisplay = document.getElementById('retry-display');    const retryReset = document.getElementById('retry-reset');    // General UI elements    const versionDisplay = document.getElementById("versionDisplay");    const addNewTaskBtn = document.getElementById('addNewTaskBtn');    const taskContainer = document.getElementById('task-container');    const noTaskPlaceholder = document.getElementById('no-task-placeholder');    const taskCardTemplate = document.getElementById('taskCardTemplate');    const downloadMenuTemplate = document.getElementById('downloadMenuTemplate');    const previewOffcanvasEl = document.getElementById('previewOffcanvas');    const previewOffcanvas = new bootstrap.Offcanvas(previewOffcanvasEl);    const previewOffcanvasLabel = document.getElementById('previewOffcanvasLabel');    const originalPreviewPane = document.getElementById('originalPreviewPane');    const translatedPreviewFrame = document.getElementById('translatedPreviewFrame');    const originalPreviewContainer = document.getElementById('originalPreviewContainer');    const translatedPreviewContainer = document.getElementById('translatedPreviewContainer');    const setBilingualViewBtn = document.getElementById('setBilingualViewBtn');    const setTranslatedOnlyViewBtn = document.getElementById('setTranslatedOnlyViewBtn');    const syncScrollBtn = document.getElementById('syncScrollBtn');    const printFrameEl = document.getElementById('printFrame');    // --- Global State ---    let defaultParams = {};    let glossaryData = {}; // [NEW] For glossary_dict    const tasks = {}; // { cardId: { elements: {...}, state: {...}, intervals: {...} } }    let isAdminMode = false;    let previewSplitInstance = null;    let isSyncScrollEnabled = false;    let isProgrammaticScroll = false;    const apiHrefMap = {        "https://api.302.ai/v1": ["https://share.302.ai/BgRLAe", "apiHrefInfo302ai"],//apiHrefInfo302ai:👈 Register through this link to enjoy a $1 free credit        "https://openrouter.ai/api/v1": ["https://openrouter.ai/settings/keys", null],        "https://api.openai.com/v1": ["https://platform.openai.com/api-keys", null],        "https://api.deepseek.com/v1": ["https://platform.deepseek.com/api_keys", null],        "https://open.bigmodel.cn/api/paas/v4": ["https://open.bigmodel.cn/usercenter/apikeys", null],        "https://dashscope.aliyuncs.com/compatible-mode/v1": ["https://bailian.console.aliyun.com/?tab=model#/api-key", null],        "https://ark.cn-beijing.volces.com/api/v3": ["https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey?apikey=%7B%7D", null],        "https://api.siliconflow.cn/v1": ["https://cloud.siliconflow.cn/account/ak", null],        "https://ai.juguang.chat/v1": ["https://ai.juguang.chat/console/token", null],        "https://www.dmxapi.cn/v1": ["https://www.dmxapi.cn/token", null],        "https://www.dmxapi.com/v1": ["https://www.dmxapi.com/console/token", null],        "https://generativelanguage.googleapis.com/v1beta/openai/": ["https://aistudio.google.com/u/0/apikey", null]    };    const workflowExtensionMap = {        'txt': 'txt',        'xlsx': 'xlsx',        'csv': 'xlsx', // Map csv input to xlsx workflow        'docx': 'docx',        'json': 'json',        'srt': 'srt',        'epub': 'epub',        'html': 'html',        'htm': 'html',        'ass': 'ass',    };    const defaultAutoWorkflow = 'markdown_based';    // --- Utility Functions ---    const generateCardId = () => `card_${Math.random().toString(36).substring(2, 10)}`;    const saveToStorage = (key, value) => {        try {            localStorage.setItem(key, value);        } catch (e) {            console.warn("Save to storage failed:", e);        }    };    const getFromStorage = (key, defaultValue = '') => {        try {            return localStorage.getItem(key) || defaultValue;        } catch (e) {            console.warn("Read from storage failed:", e);            return defaultValue;        }    };    function fileToBase64(file) {        return new Promise((resolve, reject) => {            const reader = new FileReader();            reader.readAsDataURL(file);            reader.onload = () => {                const base64String = reader.result.split(',')[1];                resolve(base64String);            };            reader.onerror = error => reject(error);        });    }    // --- [NEW] Glossary Functions ---    async function handleGlossaryFiles(event) {        const files = event.target.files;        if (!files.length) return;        let newGlossary = {};        let loadedCount = 0;        const parsePromises = Array.from(files).map(file => {            return new Promise((resolve) => {                Papa.parse(file, {                    header: true,                    skipEmptyLines: true,                    complete: (results) => {                        if (results.data) {                            results.data.forEach(row => {                                const src = row.src ? row.src.trim() : null;                                const dst = row.dst ? row.dst.trim() : null;                                // Add only if src is valid and not already in the dictionary                                if (src && dst && !newGlossary.hasOwnProperty(src)) {                                    newGlossary[src] = dst;                                }                            });                        }                        resolve();                    },                    error: (err) => {                        console.error(`Error parsing ${file.name}:`, err);                        resolve(); // Resolve anyway to not block other files                    }                });            });        });        await Promise.all(parsePromises);        glossaryData = newGlossary;        loadedCount = Object.keys(glossaryData).length;        // Simple feedback        const btnText = viewGlossaryBtn.querySelector('span');        if (btnText) {            btnText.textContent = `${getText('viewGlossaryBtn')} (${loadedCount})`;        }        console.log("Glossary loaded:", glossaryData);    }    function populateGlossaryModal() {        glossaryTableBody.innerHTML = ''; // Clear previous content        const terms = Object.entries(glossaryData);        if (terms.length === 0) {            const tr = document.createElement('tr');            const td = document.createElement('td');            td.colSpan = 2;            td.textContent = getText('glossaryEmpty');            td.className = 'text-center text-muted';            tr.appendChild(td);            glossaryTableBody.appendChild(tr);        } else {            terms.forEach(([src, dst]) => {                const tr = document.createElement('tr');                const tdSrc = document.createElement('td');                const tdDst = document.createElement('td');                tdSrc.textContent = src;                tdDst.textContent = dst;                tr.appendChild(tdSrc);                tr.appendChild(tdDst);                glossaryTableBody.appendChild(tr);            });        }    }    function clearGlossary() {        // Reset the data        glossaryData = {};        // Clear the file input so the user can re-select the same file if they want        glossaryFilesInput.value = '';        // Update the view button text to remove the count        const btnText = viewGlossaryBtn.querySelector('span');        if (btnText) {            btnText.textContent = getText('viewGlossaryBtn');        }        console.log("Glossary cleared.");        // If the modal is open, re-populate it to show it's empty        if (glossaryModalEl.classList.contains('show')) {            populateGlossaryModal();        }    }    // --- UI Update Functions based on Workflow ---    const workflowConfigs = {        'markdown_based': {            container: parsingSettingsContainer,            titleEl: parsingSettingsTitle        },        'txt': {            container: txtSettingsContainer,            titleEl: txtSettingsTitle,            modeSelect: txtInsertModeSelect,            separatorGroup: txtSeparatorGroup        },        'json': {            container: jsonSettingsContainer,            titleEl: jsonSettingsTitle        },        'xlsx': {            container: xlsxSettingsContainer,            titleEl: xlsxSettingsTitle,            modeSelect: xlsxInsertModeSelect,            separatorGroup: xlsxSeparatorGroup        },        'docx': {            container: docxSettingsContainer,            titleEl: docxSettingsTitle,            modeSelect: docxInsertModeSelect,            separatorGroup: docxSeparatorGroup        },        'srt': {            container: srtSettingsContainer,            titleEl: srtSettingsTitle,            modeSelect: srtInsertModeSelect,            separatorGroup: srtSeparatorGroup        },        'epub': {            container: epubSettingsContainer,            titleEl: epubSettingsTitle,            modeSelect: epubInsertModeSelect,            separatorGroup: epubSeparatorGroup        },        'html': {            container: htmlSettingsContainer,            titleEl: htmlSettingsTitle,            modeSelect: htmlInsertModeSelect,            separatorGroup: htmlSeparatorGroup        },        'ass': {            container: assSettingsContainer,            titleEl: assSettingsTitle,            modeSelect: assInsertModeSelect,            separatorGroup: assSeparatorGroup        }    };    function updateWorkflowUI() {        const selectedWorkflow = workflowTypeSelect.value;        // Hide all workflow-specific containers        Object.values(workflowConfigs).forEach(config => {            if (config.container) config.container.style.display = 'none';        });        // Helper to update titles with a step number        const updateTitleWithNumber = (titleElement, stepNumber) => {            if (!titleElement) return;            // First, remove any existing number span to prevent duplicates on re-renders            const existingNumber = titleElement.querySelector('.step-number');            if (existingNumber) existingNumber.remove();            // Create a new span for the number            const numberSpan = document.createElement('span');            numberSpan.className = 'step-number';            numberSpan.textContent = `${stepNumber}. `;            // Insert the number span right after the icon            const icon = titleElement.querySelector('i');            if (icon) {                icon.insertAdjacentElement('afterend', numberSpan);            } else { // Fallback if no icon                titleElement.prepend(numberSpan);            }        };        // --- Dynamic Numbering ---        let currentStep = 1;        const getStep = () => currentStep++;        // 1. Workflow Selection (Always visible)        updateTitleWithNumber(document.querySelector('#headingZero button strong'), getStep());        // 2. Show and configure the selected workflow's panel        const activeConfig = workflowConfigs[selectedWorkflow];        if (activeConfig) {            activeConfig.container.style.display = 'block';            updateTitleWithNumber(activeConfig.titleEl, getStep());            if (activeConfig.modeSelect) {                updateSeparatorVisibility(activeConfig.modeSelect, activeConfig.separatorGroup);            }        }        // 3. Renumber common sections        updateTitleWithNumber(aiSettingsTitle, getStep());        if (translationSettingsAccordionItem.style.display !== 'none') {            updateTitleWithNumber(translationSettingsTitle, getStep());        }        updateTitleWithNumber(glossaryGenSettingsTitle, getStep());        saveToStorage('translator_last_workflow', selectedWorkflow);    }    function updateSeparatorVisibility(modeSelect, separatorGroup) {        const selectedMode = modeSelect.value;        separatorGroup.style.display = (selectedMode === 'append' || selectedMode === 'prepend') ? 'block' : 'none';    }    /**     * [REFACTORED] Generic function to update custom language input UI.     * @param {HTMLSelectElement} selectEl - The language select element.     * @param {HTMLElement} groupEl - The container for the custom input.     * @param {HTMLInputElement} inputEl - The custom language text input.     */    function updateCustomLangUI(selectEl, groupEl, inputEl) {        const isCustom = selectEl.value === 'custom';        groupEl.style.display = isCustom ? 'block' : 'none';        inputEl.required = isCustom;    }    // --- Other UI Update Functions ---    function updateTranslationModeUI() {        const skipTranslate = skipTranslationSwitch.checked;        aiModelSettingsContainer.style.display = skipTranslate ? 'none' : 'block';        translationSettingsAccordionItem.style.display = skipTranslate ? 'none' : 'block';        // Make inputs not required when skipping translation        baseUrlInput.required = !skipTranslate && platformSelect.value === 'custom';        apikeyInput.required = false; // Always optional        modelInput.required = !skipTranslate;        if (!skipTranslate) {            mainPlatformUpdater();        }        updateWorkflowUI(); // Call to update numbering    }    /**     * [REFACTORED] Creates a reusable function to manage platform selection UI.     * @param {object} elements - An object containing the DOM elements for this UI section.     * @param {string} storagePrefix - The prefix for localStorage keys.     * @returns {function} An updater function to be called on change.     */    function createPlatformUIUpdater(elements, storagePrefix) {        const {            platformSelect,            apikeyInput,            modelInput,            baseUrlGroup,            baseUrlInput,            apiHref: platformApiHref,            apiHrefInfo: platformApiHrefInfo,            baseUrlDisplay        } = elements;        return () => {            const selectedPlatformValue = platformSelect.value;            apikeyInput.value = getFromStorage(`${storagePrefix}_${selectedPlatformValue}_apikey`);            modelInput.value = getFromStorage(`${storagePrefix}_${selectedPlatformValue}_model_id`);            const isCustom = selectedPlatformValue === 'custom';            baseUrlGroup.style.display = isCustom ? 'block' : 'none';            baseUrlInput.required = isCustom && !skipTranslationSwitch.checked;            baseUrlInput.value = isCustom ? getFromStorage(`${storagePrefix}_custom_base_url`) : selectedPlatformValue;            if (baseUrlDisplay) {                baseUrlDisplay.textContent = baseUrlInput.value;            }            if (platformApiHref) {                const hrefInfo = apiHrefMap[baseUrlInput.value];                if (!isCustom && hrefInfo) {                    platformApiHref.href = hrefInfo[0];                    platformApiHref.style.display = 'inline-block';                    if (platformApiHrefInfo) {                        // 使用 getText() 函数通过键来获取翻译后的文本                        platformApiHrefInfo.textContent = hrefInfo[1] ? getText(hrefInfo[1]) : '';                    }                } else {                    platformApiHref.style.display = 'none';                    if (platformApiHrefInfo) {                        platformApiHrefInfo.textContent = '';                    }                }            }            saveToStorage(`${storagePrefix}_last_platform`, selectedPlatformValue);        };    }    const mainPlatformUpdater = createPlatformUIUpdater({        platformSelect, apikeyInput, modelInput, baseUrlGroup, baseUrlInput, apiHref, apiHrefInfo,        baseUrlDisplay: mainBaseUrlDisplay    }, 'translator_platform');    function updateParsingOptionsVisibility(engine) {        const optionsConfig = {            identity: {showFormula: false, showCode: false},//选项显示：Docling            mineru: {showFormula: true, showCode: false},//选项显示：Mineru（推荐）            docling: {showFormula: true, showCode: true},//选项显示：已经是Markdown格式            mineru_deploy: {showFormula: false, showCode: false} //选项显示：Mineru部署服务        };        const config = optionsConfig[engine] || {showFormula: false, showCode: false};        formulaOcrSwitchContainer.style.display = config.showFormula ? 'block' : 'none';        codeOcrSwitchContainer.style.display = config.showCode ? 'block' : 'none';    }    function updateConvertEnginUI(isLanguageChange = false) {        if (!isLanguageChange) {            const selectedEngin = convertEnginSelect.value;            const isMineru = selectedEngin === 'mineru';            const isMineruDeploy = selectedEngin === 'mineru_deploy';            // Hide all engine-specific groups first            mineruTokenGroup.style.display = 'none';            mineruModelVersionGroup.style.display = 'none';            mineruDeployGroup.style.display = 'none';            // Show the correct group            if (isMineru) {                mineruTokenGroup.style.display = 'block';                mineruModelVersionGroup.style.display = 'block';                mineruTokenInput.value = getFromStorage('translator_mineru_token');            } else if (isMineruDeploy) {                mineruDeployGroup.style.display = 'block';            }            mineruTokenInput.required = isMineru;            mineruDeployBaseUrlInput.required = isMineruDeploy;            updateParsingOptionsVisibility(selectedEngin);            saveToStorage('translator_convert_engin', selectedEngin);        } else {            // Repopulate options with translated text            const savedValue = convertEnginSelect.value;            Array.from(convertEnginSelect.options).forEach(option => {                const keyMap = {                    'identity': 'engineOptionIdentity',                    'mineru': 'engineOptionMineru',                    'docling': 'engineOptionDocling',                    'mineru_deploy': 'engineOptionMineruDeploy'                };                option.textContent = getText(keyMap[option.value] || option.value, option.value);            });            convertEnginSelect.value = savedValue;        }    }    // [MODIFIED] Glossary Generation UI Functions    function updateGlossaryGenUI() {        const isEnabled = glossaryGenerateEnableSwitch.checked;        glossaryAgentOptionsContainer.style.display = isEnabled ? 'block' : 'none';        if (isEnabled) {            updateGlossaryCustomConfigUI();        }    }    function updateGlossaryCustomConfigUI() {        const choice = document.querySelector('input[name="glossary_agent_config_choice"]:checked').value;        glossaryAgentCustomConfigContainer.style.display = choice === 'custom' ? 'block' : 'none';    }    const glossaryAgentPlatformUpdater = createPlatformUIUpdater({        platformSelect: glossaryAgentPlatformSelect,        apikeyInput: glossaryAgentKeyInput,        modelInput: glossaryAgentModelIdInput,        baseUrlGroup: glossaryAgentBaseUrlGroup,        baseUrlInput: glossaryAgentBaseUrlInput,        baseUrlDisplay: glossaryBaseUrlDisplay    }, 'glossary_agent_platform');    function setupGlossaryAgentPlatformUI() {        glossaryAgentPlatformSelect.innerHTML = platformSelect.innerHTML;        glossaryAgentPlatformSelect.value = getFromStorage('glossary_agent_platform_last_platform', 'https://api.302.ai/v1');        glossaryAgentPlatformUpdater();    }    function createSliderUpdater(slider, display, resetBtn, key, params) {        return () => {            const value = slider.value;            display.textContent = value;            resetBtn.style.visibility = value !== String(params[key]) ? 'visible' : 'hidden';            saveToStorage(key, value);        };    }    function setupSlider(slider, display, resetBtn, key, params) {        slider.value = getFromStorage(key, params[key]);        const updater = createSliderUpdater(slider, display, resetBtn, key, params);        slider.addEventListener('input', updater);        resetBtn.addEventListener('click', () => {            slider.value = params[key];            updater();        });        updater(); // Initial call    }    function updateTaskPlaceholderVisibility() {        noTaskPlaceholder.style.display = Object.keys(tasks).length === 0 ? 'block' : 'none';    }    function saveTaskIds() {        if (isAdminMode) return;        const submittedTaskIds = Object.values(tasks)            .map(task => task.state.backendTaskId)            .filter(id => id);        saveToStorage('active_task_ids', JSON.stringify(submittedTaskIds));    }    // --- Task Card Management ---    function createTaskCard(backendTaskId = null, restoreState = false) {        const cardId = backendTaskId || generateCardId();        if (tasks[cardId]) return; // Avoid duplicate cards        const cardFragment = taskCardTemplate.content.cloneNode(true);        const cardElement = cardFragment.querySelector('.task-card');        cardElement.dataset.cardId = cardId;        // Translate the new card immediately        const translations = i18nData[currentLang] || i18nData.zh;        cardElement.querySelectorAll('[data-i18n]').forEach(el => el.innerHTML = translations[el.dataset.i18n] || el.innerHTML);        cardElement.querySelectorAll('[data-i18n-placeholder]').forEach(el => el.placeholder = translations[el.dataset.i18nPlaceholder] || el.placeholder);        cardElement.querySelectorAll('[data-i18n-title]').forEach(el => {            const key = el.getAttribute('data-i18n-title');            if (translations[key]) el.title = translations[key];        });        const elements = {            card: cardElement,            taskIdDisplay: cardElement.querySelector('.task-id-display'),            removeBtn: cardElement.querySelector('.remove-task-btn'),            fileInput: cardElement.querySelector('.file-input'),            fileDropArea: cardElement.querySelector('.file-drop-area'),            fileDropDefault: cardElement.querySelector('.file-drop-default'),            fileDropSelected: cardElement.querySelector('.file-drop-selected'),            fileNameDisplayWrapper: cardElement.querySelector('.file-name-display-wrapper'),            fileNameDisplay: cardElement.querySelector('.file-name-display'),            logArea: cardElement.querySelector('.log-area'),            copyLogBtn: cardElement.querySelector('.copy-log-btn'),            statusMessage: cardElement.querySelector('.status-message'),            downloadButtons: cardElement.querySelector('.download-buttons'),            downloadMenuContainer: cardElement.querySelector('.download-menu-container'),            previewBtn: cardElement.querySelector('.preview-html-btn'),            startBtn: cardElement.querySelector('.start-translate-btn'),            // [NEW] Attachment elements            attachmentBtnGroup: cardElement.querySelector('.attachment-btn-group'),            attachmentMenuContainer: cardElement.querySelector('.attachment-menu-container'),        };        if (restoreState && backendTaskId) {            elements.taskIdDisplay.innerHTML = `<code>${backendTaskId}</code>`;        }        tasks[cardId] = {            elements,            state: {                backendTaskId: backendTaskId,                isTranslating: false,                file: null,                htmlUrl: null,                fileNameStem: null,                isSubmitted: restoreState,                downloads: {},                attachment: {},                workflow: null            },            intervals: {                log: null,                status: null            }        };        addEventListenersToCard(cardId);        taskContainer.prepend(cardElement);        new bootstrap.Tooltip(elements.copyLogBtn);        updateTaskPlaceholderVisibility();        if (restoreState && backendTaskId) {            pollStatus(backendTaskId, true);        }    }    async function releaseTask(backendTaskId) {        try {            await fetch(`/service/release/${backendTaskId}`, {method: 'POST'});        } catch (error) {            console.error(`[${backendTaskId}] Failed to send release request:`, error);        }    }    async function removeTask(cardId) {        const task = tasks[cardId];        if (!task) return;        const backendTaskId = task.state.backendTaskId;        if (backendTaskId) {            stopPolling(backendTaskId);            await releaseTask(backendTaskId);        }        task.elements.card.remove();        delete tasks[cardId];        saveTaskIds();        updateTaskPlaceholderVisibility();    }    function addEventListenersToCard(cardId) {        const {elements} = tasks[cardId];        elements.card.addEventListener('click', () => {            const task = tasks[cardId];            if (task && task.state.workflow) {                workflowTypeSelect.value = task.state.workflow;                updateWorkflowUI();            }        });        elements.removeBtn.addEventListener('click', () => removeTask(cardId));        elements.fileDropArea.addEventListener('click', () => elements.fileInput.click());        elements.fileInput.addEventListener('change', () => handleFileSelect(cardId));        ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {            elements.fileDropArea.addEventListener(eventName, e => {                e.preventDefault();                e.stopPropagation();            }, false);        });        ['dragenter', 'dragover'].forEach(eventName => {            elements.fileDropArea.addEventListener(eventName, () => elements.fileDropArea.classList.add('drag-over'), false);        });        ['dragleave', 'drop'].forEach(eventName => {            elements.fileDropArea.addEventListener(eventName, () => elements.fileDropArea.classList.remove('drag-over'), false);        });        elements.fileDropArea.addEventListener('drop', e => {            if (e.dataTransfer.files.length > 0) {                elements.fileInput.files = e.dataTransfer.files;                handleFileSelect(cardId);            }        }, false);        elements.startBtn.addEventListener('click', () => {            if (tasks[cardId].state.isTranslating) {                cancelTranslation(cardId);            } else {                startTranslation(cardId);            }        });        elements.copyLogBtn.addEventListener('click', (e) => {            e.stopPropagation(); // Prevent card click event from firing            const logText = elements.logArea.innerText;            if (!logText) return;            navigator.clipboard.writeText(logText).then(() => {                const icon = elements.copyLogBtn.querySelector('i');                icon.classList.replace('bi-clipboard', 'bi-check-lg');                elements.copyLogBtn.classList.replace('btn-outline-secondary', 'btn-success');                setTimeout(() => {                    icon.classList.replace('bi-check-lg', 'bi-clipboard');                    elements.copyLogBtn.classList.replace('btn-success', 'btn-outline-secondary');                }, 2000);            }).catch(err => {                console.error('Failed to copy logs: ', err);                elements.copyLogBtn.classList.replace('btn-outline-secondary', 'btn-danger');                setTimeout(() => {                    elements.copyLogBtn.classList.replace('btn-danger', 'btn-outline-secondary');                }, 2000);            });        });    }    function handleFileSelect(cardId) {        const {elements, state} = tasks[cardId];        const file = elements.fileInput.files[0];        if (file) {            state.file = file;            elements.fileNameDisplay.textContent = file.name;            elements.fileNameDisplayWrapper.style.display = 'block';            elements.fileDropArea.classList.add('file-selected');            elements.fileDropDefault.style.display = 'none';            elements.fileDropSelected.style.display = 'block';            elements.fileDropArea.classList.remove('input-error');            elements.fileNameDisplay.classList.remove('input-error-text');            if (autoWorkflowSwitch.checked) {                const fileExtension = file.name.split('.').pop().toLowerCase();                const targetWorkflow = workflowExtensionMap[fileExtension] || defaultAutoWorkflow;                workflowTypeSelect.value = targetWorkflow;                updateWorkflowUI();            }        }    }    function buildGlossaryAgentConfig() {        if (!glossaryGenerateEnableSwitch.checked) {            return {config: null, isValid: true};        }        const agentChoice = document.querySelector('input[name="glossary_agent_config_choice"]:checked').value;        // The custom prompt for the glossary agent is now always visible and should always be read.        const glossaryCustomPrompt = glossaryAgentCustomPromptTextarea.value || null;        if (agentChoice === 'same') {            // [MODIFIED LOGIC]            // Construct the agent config by mirroring the main translation settings,            // but using the dedicated glossary custom prompt.            const targetLanguage = toLangSelect.value === 'custom' ? customLangInput.value.trim() : toLangSelect.value;            const agentConfig = {                base_url: baseUrlInput.value,                api_key: apikeyInput.value,                model_id: modelInput.value,                to_lang: targetLanguage,                custom_prompt: glossaryCustomPrompt, // This is the only override from the dedicated field                temperature: parseFloat(temperatureSlider.value),                concurrent: parseInt(concurrentSlider.value, 10),                retry: parseInt(retrySlider.value, 10),                thinking: document.querySelector('input[name="thinking"]:checked')?.value || 'default',                system_proxy_enable: systemProxyEnableSwitch.checked,                chunk_size: parseInt(chunkSizeSlider.value, 10) // Use main chunk size            };            // No validation needed here, as the main form validation will catch any issues.            return {config: agentConfig, isValid: true};        }        // Handle 'custom' config        let isValid = true;        const requiredAgentInputs = [glossaryAgentModelIdInput];        if (glossaryAgentPlatformSelect.value === 'custom') {            requiredAgentInputs.push(glossaryAgentBaseUrlInput);        }        requiredAgentInputs.forEach(input => {            input.classList.remove('is-invalid');            if (!input.value.trim()) {                input.classList.add('is-invalid');                isValid = false;            }        });        let glossaryTargetLanguage = glossaryAgentToLangSelect.value;        if (glossaryTargetLanguage === 'custom') {            glossaryTargetLanguage = glossaryAgentCustomLangInput.value.trim();            glossaryAgentCustomLangInput.classList.remove('is-invalid');            if (!glossaryTargetLanguage) {                glossaryAgentCustomLangInput.classList.add('is-invalid');                isValid = false;            }        }        if (!isValid) {            return {config: null, isValid: false};        }        // Build the agentConfig object from the custom form.        const agentConfig = {            base_url: glossaryAgentBaseUrlInput.value,            api_key: glossaryAgentKeyInput.value,            model_id: glossaryAgentModelIdInput.value,            to_lang: glossaryTargetLanguage,            custom_prompt: glossaryCustomPrompt, // Use value from the now-independent prompt field            temperature: parseFloat(glossaryAgentTemperatureSlider.value),            concurrent: parseInt(glossaryAgentConcurrentSlider.value, 10),            retry: parseInt(glossaryAgentRetrySlider.value, 10),            thinking: document.querySelector('input[name="glossary_agent_thinking"]:checked')?.value || 'default',            system_proxy_enable: glossaryAgentSystemProxyEnableSwitch.checked,            chunk_size: parseInt(glossaryAgentChunkSizeSlider.value, 10) // Use custom chunk size        };        return {config: agentConfig, isValid: true};    }    function validateAndBuildPayload() {        const skipTranslate = skipTranslationSwitch.checked;        let isValid = true;        // --- Common settings validation ---        if (!skipTranslate) {            // API key is not required anymore, so it's removed from validation.            const requiredCommonInputs = [modelInput];            if (platformSelect.value === 'custom') requiredCommonInputs.push(baseUrlInput);            requiredCommonInputs.forEach(input => {                input.classList.remove('is-invalid');                if (!input.value.trim()) {                    input.classList.add('is-invalid');                    isValid = false;                }            });            if (toLangSelect.value === 'custom') {                customLangInput.classList.remove('is-invalid');                if (!customLangInput.value.trim()) {                    customLangInput.classList.add('is-invalid');                    isValid = false;                }            }        }        // --- Base Payload Construction ---        const targetLanguage = toLangSelect.value === 'custom' ? customLangInput.value.trim() : toLangSelect.value;        const basePayload = {            skip_translate: skipTranslate,            base_url: baseUrlInput.value,            api_key: apikeyInput.value,            model_id: modelInput.value,            to_lang: targetLanguage,            thinking: document.querySelector('input[name="thinking"]:checked')?.value || 'disable',            chunk_size: parseInt(chunkSizeSlider.value, 10),            concurrent: parseInt(concurrentSlider.value, 10),            temperature: parseFloat(temperatureSlider.value),            retry: parseInt(retrySlider.value, 10),            custom_prompt: customPromptTranslateArea.value || null,            glossary_dict: Object.keys(glossaryData).length > 0 ? glossaryData : null,            system_proxy_enable: systemProxyEnableSwitch.checked,        };        // --- Glossary Generation Config ---        const glossaryResult = buildGlossaryAgentConfig();        if (!glossaryResult.isValid) isValid = false;        basePayload.glossary_generate_enable = glossaryGenerateEnableSwitch.checked;        basePayload.glossary_agent_config = glossaryResult.config;        // --- Workflow-specific validation and payload building ---        const workflowType = workflowTypeSelect.value;        let workflowPayload = {...basePayload, workflow_type: workflowType};        switch (workflowType) {            case 'markdown_based': {                const engine = convertEnginSelect.value;                mineruTokenInput.classList.remove('is-invalid');                mineruDeployBaseUrlInput.classList.remove('is-invalid');                if (engine === 'mineru' && !mineruTokenInput.value.trim()) {                    mineruTokenInput.classList.add('is-invalid');                    isValid = false;                } else if (engine === 'mineru_deploy' && !mineruDeployBaseUrlInput.value.trim()) {                    mineruDeployBaseUrlInput.classList.add('is-invalid');                    isValid = false;                }                Object.assign(workflowPayload, {                    convert_engine: engine || null,                    mineru_token: mineruTokenInput.value || null,                    formula_ocr: formulaCheckbox.checked,                    code_ocr: codeCheckbox.checked,                    model_version: modelVersionSelect.value,                    // Add new deploy params                    mineru_deploy_base_url: mineruDeployBaseUrlInput.value || null,                    mineru_deploy_backend: mineruDeployBackendSelect.value,                    mineru_deploy_formula_enable: mineruDeployFormulaEnableSwitch.checked,                    mineru_deploy_start_page_id: parseInt(mineruDeployStartPageInput.value, 10),                    mineru_deploy_end_page_id: parseInt(mineruDeployEndPageInput.value, 10),                });                break;            }            case 'json': {                const jsonPaths = jsonPathsTextarea.value.trim().split('\n').map(p => p.trim()).filter(p => p);                if (jsonPaths.length === 0) {                    jsonPathsTextarea.classList.add('is-invalid');                    isValid = false;                } else {                    jsonPathsTextarea.classList.remove('is-invalid');                }                workflowPayload.json_paths = jsonPaths;                break;            }            case 'xlsx': {                const translateRegions = xlsxTranslateRegionsTextarea.value.trim().split('\n').map(p => p.trim()).filter(p => p);                Object.assign(workflowPayload, {                    insert_mode: xlsxInsertModeSelect.value,                    separator: xlsxSeparatorInput.value.replace(/\\n/g, '\n'),                    translate_regions: translateRegions.length > 0 ? translateRegions : null                });                break;            }            case 'txt':            case 'docx':            case 'srt':            case 'epub':            case 'html':            case 'ass': {                const controls = {                    txt: {mode: txtInsertModeSelect, sep: txtSeparatorInput},                    docx: {mode: docxInsertModeSelect, sep: docxSeparatorInput},                    srt: {mode: srtInsertModeSelect, sep: srtSeparatorInput},                    epub: {mode: epubInsertModeSelect, sep: epubSeparatorInput},                    html: {mode: htmlInsertModeSelect, sep: htmlSeparatorInput},                    ass: {mode: assInsertModeSelect, sep: assSeparatorInput},                }[workflowType];                let separatorValue = controls.sep.value;                if (workflowType !== 'ass') {                    separatorValue = separatorValue.replace(/\\n/g, '\n');                }                Object.assign(workflowPayload, {                    insert_mode: controls.mode.value,                    separator: separatorValue                });                break;            }        }        return {payload: isValid ? workflowPayload : null, isValid};    }    async function startTranslation(cardId) {        const {elements, state} = tasks[cardId];        if (!state.file) {            elements.statusMessage.textContent = getText('status_selectFileFirst');            elements.statusMessage.className = 'status-message small text-danger';            elements.fileDropArea.classList.add('input-error');            return;        }        const {payload, isValid} = validateAndBuildPayload();        state.workflow = workflowTypeSelect.value; // Store the workflow type for the task        if (!isValid) {            elements.statusMessage.textContent = getText('status_fillRequired');            elements.statusMessage.className = 'status-message small text-danger';            // Find the first invalid input and scroll to it            const firstInvalid = document.querySelector('.is-invalid, .input-error');            if (firstInvalid) {                firstInvalid.scrollIntoView({behavior: 'smooth', block: 'center'});            }            return;        }        // --- Release old task and start new one ---        const oldBackendTaskId = state.backendTaskId;        if (oldBackendTaskId) {            await releaseTask(oldBackendTaskId);            state.backendTaskId = null;        }        state.isTranslating = true;        elements.startBtn.disabled = true;        elements.startBtn.innerHTML = `<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> <span data-i18n="btn_initializing">${getText('btn_initializing')}</span>`;        elements.logArea.innerHTML = '';        elements.statusMessage.textContent = getText('status_encodingAndSubmitting');        elements.statusMessage.className = 'status-message small text-muted';        elements.downloadButtons.style.display = 'none';        elements.card.querySelector('.progress')?.remove(); // Make sure no old progress bar exists        // Temporarily add a spinner next to the status message        elements.statusMessage.insertAdjacentHTML('afterend', '<div class="spinner-border spinner-border-sm ms-2" role="status" id="temp-spinner-' + cardId + '"><span class="visually-hidden">Loading...</span></div>');        try {            const fileContentBase64 = await fileToBase64(state.file);            const finalRequest = {                file_name: state.file.name,                file_content: fileContentBase64,                payload: payload            };            const response = await fetch('/service/translate', {                method: 'POST',                headers: {'Content-Type': 'application/json'},                body: JSON.stringify(finalRequest)            });            const result = await response.json();            if (response.ok && result.task_started) {                const backendTaskId = result.task_id;                state.backendTaskId = backendTaskId;                state.isSubmitted = true;                elements.taskIdDisplay.innerHTML = `<code>${backendTaskId}</code>`;                elements.taskIdDisplay.classList.remove('task-id-placeholder');                saveTaskIds();                elements.statusMessage.textContent = result.message || getText('status_requestOk');                elements.statusMessage.className = 'status-message small text-info';                elements.startBtn.innerHTML = `<i class="bi bi-stop-circle-fill me-1"></i><span data-i18n="btn_cancelTranslation">${getText('btn_cancelTranslation')}</span>`;                elements.startBtn.classList.replace('btn-primary', 'btn-danger');                elements.startBtn.disabled = false;                startPolling(backendTaskId);            } else {                let errorMessage = result.detail || result.message || `${getText('status_requestFail')} (${response.status})`;                if (typeof errorMessage === 'object') errorMessage = JSON.stringify(errorMessage);                throw new Error(errorMessage);            }        } catch (error) {            state.isSubmitted = false;            console.error('Request failed:', error);            elements.statusMessage.textContent = `${getText('status_initFail')}: ${error.message}`;            elements.statusMessage.className = 'status-message small text-danger';            elements.startBtn.innerHTML = `<i class="bi bi-play-fill me-1"></i><span data-i8n="taskCardStartBtn">${getText('taskCardStartBtn')}</span>`;            elements.startBtn.classList.replace('btn-danger', 'btn-primary');            elements.startBtn.disabled = false;            document.getElementById('temp-spinner-' + cardId)?.remove();            state.isTranslating = false;        }    }    async function cancelTranslation(cardId) {        const task = tasks[cardId];        if (!task || !task.state.backendTaskId) return;        const {elements} = task;        const backendTaskId = task.state.backendTaskId;        elements.startBtn.disabled = true;        elements.startBtn.innerHTML = `<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> <span data-i18n="status_cancelling">${getText('status_cancelling')}</span>`;        try {            const response = await fetch(`/service/cancel/${backendTaskId}`, {method: 'POST'});            const result = await response.json();            if (response.ok && result.cancelled) {                elements.statusMessage.textContent = result.message || getText('status_cancelSent');                elements.statusMessage.className = 'status-message small text-warning';            } else {                throw new Error(result.message || getText('status_cancelFail'));            }        } catch (error) {            elements.statusMessage.textContent = `${getText('status_cancelFail')}: ${error.message}`;            elements.statusMessage.className = 'status-message small text-danger';            elements.startBtn.disabled = false;            elements.startBtn.innerHTML = `<i class="bi bi-stop-circle-fill me-1"></i><span data-i18n="btn_cancelTranslation">${getText('btn_cancelTranslation')}</span>`;        }    }    // --- Polling ---    function startPolling(backendTaskId) {        stopPolling(backendTaskId);        const card = Object.values(tasks).find(t => t.state.backendTaskId === backendTaskId);        if (!card) return;        card.intervals.log = setInterval(() => pollLogs(backendTaskId), 2000);        card.intervals.status = setInterval(() => pollStatus(backendTaskId), 1500);        pollLogs(backendTaskId);        pollStatus(backendTaskId);    }    function stopPolling(backendTaskId) {        const card = Object.values(tasks).find(t => t.state.backendTaskId === backendTaskId);        if (!card) return;        const {intervals} = card;        if (intervals.log) clearInterval(intervals.log);        if (intervals.status) clearInterval(intervals.status);        intervals.log = null;        intervals.status = null;    }    async function pollLogs(backendTaskId) {        const card = Object.values(tasks).find(t => t.state.backendTaskId === backendTaskId);        if (!card) return;        const {elements} = card;        try {            const response = await fetch(`/service/logs/${backendTaskId}`);            if (!response.ok) return;            const data = await response.json();            if (data.logs && data.logs.length > 0) {                elements.logArea.innerHTML += data.logs.map(log => log.replace(/</g, "<").replace(/>/g, ">") + '<br>').join('');                elements.logArea.scrollTop = elements.logArea.scrollHeight;            }        } catch (error) {            console.warn(`[${backendTaskId}] Error polling logs:`, error);        }    }    async function pollStatus(backendTaskId, isRestore = false) {        const card = Object.values(tasks).find(t => t.state.backendTaskId === backendTaskId);        if (!card) {            if (isRestore) {                console.warn(`Restored task ${backendTaskId} not found in UI, removing from storage.`);                const savedTaskIds = JSON.parse(getFromStorage('active_task_ids', '[]'));                const newIds = savedTaskIds.filter(id => id !== backendTaskId);                saveToStorage('active_task_ids', JSON.stringify(newIds));            }            return;        }        const {elements, state} = card;        const cardId = elements.card.dataset.cardId;        try {            const response = await fetch(`/service/status/${backendTaskId}`);            if (!response.ok) {                if (response.status === 404 && isRestore) {                    console.warn(`Task ${backendTaskId} not found on server (404). Removing from UI.`);                    await removeTask(cardId);                }                return;            }            const status = await response.json();            if (status.original_filename && (!state.file || isRestore)) {                elements.fileNameDisplay.textContent = status.original_filename;                elements.fileNameDisplayWrapper.style.display = 'block';            }            elements.statusMessage.textContent = status.status_message || getText('status_gettingStatus');            elements.statusMessage.className = `status-message small ${status.error_flag ? 'text-danger' : 'text-info'}`;            if (!status.is_processing) {                pollLogs(backendTaskId); // Get final logs                stopPolling(backendTaskId);                state.isTranslating = false;                elements.startBtn.disabled = false;                elements.startBtn.innerHTML = `<i class="bi bi-arrow-clockwise me-1"></i><span data-i18n="btn_reTranslate">${getText('btn_reTranslate')}</span>`;                elements.startBtn.classList.replace('btn-danger', 'btn-primary');                document.getElementById('temp-spinner-' + cardId)?.remove();                if (status.download_ready && !status.error_flag) {                    elements.statusMessage.className = 'status-message small text-success';                    updateResultButtons(cardId, status);                } else {                    elements.downloadButtons.style.display = 'none';                }            } else {                state.isTranslating = true;                elements.startBtn.innerHTML = `<i class="bi bi-stop-circle-fill me-1"></i><span data-i18n="btn_cancelTranslation">${getText('btn_cancelTranslation')}</span>`;                elements.startBtn.classList.replace('btn-primary', 'btn-danger');                elements.startBtn.disabled = false;                elements.downloadButtons.style.display = 'none';                if (isRestore && !card.intervals.status) {                    startPolling(backendTaskId);                }            }        } catch (error) {            console.error(`[${backendTaskId}] Error polling status:`, error);            elements.statusMessage.textContent = getText('status_updateError');            elements.statusMessage.className = 'status-message small text-danger';        }    }    // --- Download and Preview ---    function populateDownloadMenu(menuElement, downloads, cardId) {        menuElement.innerHTML = ''; // Clear existing items        if (!downloadMenuTemplate) return false;        const content = downloadMenuTemplate.content.cloneNode(true);        // Translate the template before using        content.querySelectorAll('[data-i18n]').forEach(el => el.innerHTML = getText(el.dataset.i18n));        let anyLinkAdded = false;        const setupLink = (selector, key) => {            const li = content.querySelector(selector);            if (li && downloads[key]) {                li.querySelector('a').href = downloads[key];                menuElement.appendChild(li);                anyLinkAdded = true;            }        };        setupLink('.download-item-md', 'markdown');        setupLink('.download-item-md-zip', 'markdown_zip');        setupLink('.download-item-txt', 'txt');        setupLink('.download-item-json', 'json');        setupLink('.download-item-docx', 'docx');        setupLink('.download-item-xlsx', 'xlsx');        setupLink('.download-item-csv', 'csv');        setupLink('.download-item-srt', 'srt');        setupLink('.download-item-epub', 'epub');        setupLink('.download-item-ass', 'ass');        setupLink('.download-item-html', 'html');        // Special handler for PDF, which is generated on the fly        const pdfLi = content.querySelector('.download-item-pdf');        if (pdfLi && downloads.html) {            const pdfLink = pdfLi.querySelector('a');            pdfLink.href = '#';            pdfLink.onclick = (e) => {                e.preventDefault();                downloadPdf(cardId);            };            menuElement.appendChild(pdfLi);            anyLinkAdded = true;        }        return anyLinkAdded;    }    function updateResultButtons(cardId, status) {        const {elements, state} = tasks[cardId];        const {downloads, attachment} = status;        state.downloads = downloads;        state.attachment = attachment;        const {            previewBtn,            downloadButtons,            downloadMenuContainer,            attachmentBtnGroup,            attachmentMenuContainer        } = elements;        // Reset visibility of individual components first        previewBtn.style.display = 'none';        attachmentBtnGroup.style.display = 'none';        let anyDownloadAvailable = false;        let anyAttachmentAvailable = false;        let anyPreviewAvailable = false;        // Handle preview        if (downloads && downloads.html) {            state.htmlUrl = downloads.html;            state.fileNameStem = status.original_filename_stem;            previewBtn.style.display = 'inline-block';            previewBtn.onclick = () => setupPreview(cardId);            anyPreviewAvailable = true;        }        // Handle standard downloads        if (downloads) {            anyDownloadAvailable = populateDownloadMenu(downloadMenuContainer, downloads, cardId);        }        // Handle attachments        if (attachment && Object.keys(attachment).length > 0) {            attachmentMenuContainer.innerHTML = ''; // Clear previous items            Object.entries(attachment).forEach(([identifier, url]) => {                const li = document.createElement('li');                const a = document.createElement('a');                a.className = 'dropdown-item';                a.href = url;                // A simple icon and the identifier text                a.innerHTML = `<i class="bi bi-file-earmark-arrow-down me-2"></i>${identifier}`;                li.appendChild(a);                attachmentMenuContainer.appendChild(li);            });            attachmentBtnGroup.style.display = 'inline-block';            anyAttachmentAvailable = true;        }        // Set visibility of the main container        if (anyPreviewAvailable || anyDownloadAvailable || anyAttachmentAvailable) {            downloadButtons.style.display = 'flex'; // Use flex for proper alignment        } else {            downloadButtons.style.display = 'none';        }    }    function setupPreview(cardId) {        const {state} = tasks[cardId];        if (!state.htmlUrl) return;        const existingOriginalContent = originalPreviewPane.querySelector('iframe, pre, p');        if (existingOriginalContent) existingOriginalContent.remove();        translatedPreviewFrame.src = 'about:blank';        translatedPreviewFrame.srcdoc = getText('preview_loading');        setPreviewDisplayMode('bilingual');        previewOffcanvas.show();        const menu = document.getElementById('previewDownloadMenu');        populateDownloadMenu(menu, state.downloads, cardId);        if (state.file) {            const fileType = state.file.type;            const fileExtension = state.file.name.split('.').pop().toLowerCase();            const textLikeExtensions = ['md', 'json', 'xml', 'log', 'py', 'js', 'css', 'java', 'c', 'cpp', 'h', 'hpp', 'cs', 'rb', 'php', 'swift', 'kt', 'go', 'rs', 'ts', 'txt', 'srt', 'ass'];            if (fileType.startsWith('text/') || textLikeExtensions.includes(fileExtension)) {                const pre = document.createElement('pre');                state.file.text()                    .then(text => pre.textContent = text)                    .catch(() => pre.textContent = getText('preview_cantReadOriginal'));                originalPreviewPane.appendChild(pre);            } else if (['application/pdf', 'text/html'].includes(fileType) || ['html', 'htm'].includes(fileExtension)) {                const iframe = document.createElement('iframe');                iframe.src = URL.createObjectURL(state.file);                originalPreviewPane.appendChild(iframe);            } else {                const p = document.createElement('p');                p.className = 'p-3 text-muted';                p.textContent = `${getText('preview_cantPreviewType')} (${fileType || 'unknown: ' + fileExtension}).`;                originalPreviewPane.appendChild(p);            }        } else {            const p = document.createElement('p');            p.className = 'p-3 text-muted';            p.textContent = getText('preview_noOriginalCache');            originalPreviewPane.appendChild(p);        }        fetch(state.htmlUrl)            .then(resp => resp.ok ? resp.text() : Promise.reject(`HTTP error ${resp.status}`))            .then(html => {                translatedPreviewFrame.srcdoc = html;            })            .catch(err => {                console.error('Preview fetch failed:', err);                translatedPreviewFrame.srcdoc = `${getText('preview_loadFailed')}<p>${err.message}</p>`;            });    }    function downloadPdf(cardId) {        const {elements, state} = tasks[cardId];        if (!state.htmlUrl) return;        const toast = new bootstrap.Toast(document.createElement('div'), {            autohide: true,            delay: 3000        });        toast._element.classList.add('toast', 'position-fixed', 'top-0', 'end-0', 'p-3', 'bg-info', 'text-white');        toast._element.innerHTML = `<div class="toast-body">${getText('pdf_preparing')}</div>`;        document.body.appendChild(toast._element);        toast.show();        fetch(state.htmlUrl)            .then(resp => resp.ok ? resp.text() : Promise.reject(`HTTP error ${resp.status}`))            .then(html => {                printFrameEl.onload = () => {                    setTimeout(() => {                        try {                            printFrameEl.contentWindow.focus();                            printFrameEl.contentWindow.print();                        } catch (err) {                            alert(getText('pdf_print_failed'));                        } finally {                            printFrameEl.onload = null;                            printFrameEl.srcdoc = ''; // Clear content                        }                    }, 500);                };                printFrameEl.srcdoc = html;            })            .catch(err => {                alert(getText('pdf_fetch_failed'));            });    }    function setPreviewDisplayMode(mode) {        if (previewSplitInstance) {            previewSplitInstance.destroy();            previewSplitInstance = null;        }        const isMobileView = window.innerWidth < 992;        const splitContainer = document.querySelector('.preview-split-container');        originalPreviewContainer.style.display = 'flex';        translatedPreviewContainer.style.display = 'flex';        [originalPreviewContainer, translatedPreviewContainer].forEach(el => {            el.style.width = '';            el.style.height = '';        });        splitContainer.style.flexDirection = isMobileView ? 'column' : 'row';        if (mode === 'bilingual') {            previewOffcanvasLabel.textContent = getText('preview_bilingual');            if (isMobileView) {                previewSplitInstance = Split(['#originalPreviewContainer', '#translatedPreviewContainer'], {                    direction: 'vertical', sizes: [50, 50], minSize: 150, gutterSize: 10, cursor: 'row-resize',                });            } else {                previewSplitInstance = Split(['#originalPreviewContainer', '#translatedPreviewContainer'], {                    direction: 'horizontal', sizes: [50, 50], minSize: 200, gutterSize: 10, cursor: 'col-resize',                });            }            setBilingualViewBtn.classList.add('btn-primary');            setBilingualViewBtn.classList.remove('btn-outline-primary');            setTranslatedOnlyViewBtn.classList.remove('btn-primary');            setTranslatedOnlyViewBtn.classList.add('btn-outline-primary');        } else { // mode === 'translatedOnly'            previewOffcanvasLabel.textContent = getText('preview_translatedOnly');            originalPreviewContainer.style.display = 'none';            if (isMobileView) {                translatedPreviewContainer.style.height = '100%';            } else {                translatedPreviewContainer.style.width = '100%';            }            setTranslatedOnlyViewBtn.classList.add('btn-primary');            setTranslatedOnlyViewBtn.classList.remove('btn-outline-primary');            setBilingualViewBtn.classList.remove('btn-primary');            setBilingualViewBtn.classList.add('btn-outline-primary');        }    }    function syncScroll(source, target) {        if (isProgrammaticScroll || !isSyncScrollEnabled) return;        const sourceScrollable = source.scrollHeight - source.clientHeight;        if (sourceScrollable <= 0) return;        const scrollPercentage = source.scrollTop / sourceScrollable;        const targetScrollable = target.scrollHeight - target.clientHeight;        const targetScrollTop = scrollPercentage * targetScrollable;        isProgrammaticScroll = true;        target.scrollTop = targetScrollTop;        requestAnimationFrame(() => {            isProgrammaticScroll = false;        });    }    function setupSyncScrollListeners() {        let originalScroller = originalPreviewPane;        let translatedScroller = null;        const originalScrollListener = () => {            if (translatedScroller) {                syncScroll(originalScroller, translatedScroller);            }        };        const translatedScrollListener = () => {            if (translatedScroller) {                syncScroll(translatedScroller, originalScroller);            }        };        originalScroller.addEventListener('scroll', originalScrollListener);        translatedPreviewFrame.addEventListener('load', () => {            try {                const doc = translatedPreviewFrame.contentDocument;                const win = translatedPreviewFrame.contentWindow;                if (doc && win) {                    translatedScroller = doc.scrollingElement || doc.documentElement;                    win.addEventListener('scroll', translatedScrollListener);                }            } catch (e) {                console.error('[SyncScroll] Error attaching listener to iframe:', e);            }        });        syncScrollBtn.addEventListener('click', () => {            isSyncScrollEnabled = !isSyncScrollEnabled;            syncScrollBtn.classList.toggle('active', isSyncScrollEnabled);            syncScrollBtn.classList.toggle('btn-primary', isSyncScrollEnabled);            syncScrollBtn.classList.toggle('btn-outline-secondary', !isSyncScrollEnabled);            syncScrollBtn.querySelector('i').classList.toggle('bi-link-45deg', !isSyncScrollEnabled);            syncScrollBtn.querySelector('i').classList.toggle('bi-link', isSyncScrollEnabled);            saveToStorage('ui_sync_scroll_enabled', isSyncScrollEnabled);        });        // Restore saved state from localStorage on init        if (getFromStorage('ui_sync_scroll_enabled', 'false') === 'true') {            isSyncScrollEnabled = true;            syncScrollBtn.classList.add('active', 'btn-primary');            syncScrollBtn.classList.remove('btn-outline-secondary');            syncScrollBtn.querySelector('i').classList.remove('bi-link-45deg');            syncScrollBtn.querySelector('i').classList.add('bi-link');        }    }    // --- End of Synchronized Scrolling Logic ---    // --- Password Toggle Functionality ---    function setupPasswordToggle(button) {        const targetId = button.dataset.target;        const passwordInput = document.getElementById(targetId);        const icon = button.querySelector('i');        button.addEventListener('click', () => {            if (passwordInput.type === 'password') {                passwordInput.type = 'text';                icon.classList.remove('bi-eye-slash');                icon.classList.add('bi-eye');            } else {                passwordInput.type = 'password';                icon.classList.remove('bi-eye');                icon.classList.add('bi-eye-slash');            }        });    }    // --- Initialization ---    /**     * [REFACTORED] Restores user settings from localStorage using a configuration array.     */    function restoreSettings(settingsConfig) {        settingsConfig.forEach(setting => {            const savedValue = getFromStorage(setting.key, setting.defaultValue);            if (setting.type === 'boolean') {                setting.element.checked = savedValue === 'true';            } else if (setting.type === 'radio') {                const radioToSelect = document.querySelector(`${setting.selector}[value="${savedValue}"]`);                if (radioToSelect) radioToSelect.checked = true;            } else {                setting.element.value = savedValue;            }        });    }    /**     * [REFACTORED] Sets up event listeners for saving settings using a configuration array.     */    function setupEventListeners(listenersConfig) {        listenersConfig.forEach(config => {            const handler = config.handler || ((e) => {                const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;                if (config.saveKey) saveToStorage(config.saveKey, value);                if (config.postHook) config.postHook();            });            config.element.addEventListener(config.event, handler);        });    }    async function init() {        // Step 1: Fetch i18n data        try {            const response = await fetch("/static/i18nData.json");            if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);            i18nData = await response.json();        } catch (error) {            console.error("Fatal: Failed to load i18n data.", error);            currentLang = getFromStorage('ui_language') || (navigator.language.toLowerCase().startsWith('en') ? 'en' : 'zh');            alert(getText('init_i18n_failed_alert'));            return;        }        // Step 2: Fetch other backend data        isAdminMode = window.location.pathname.includes('/admin');        try {            const [metaRes, enginRes, paramsRes] = await Promise.all([                fetch("/service/meta"), fetch('/service/engin-list'), fetch("/service/default-params")            ]);            if (!metaRes.ok || !enginRes.ok || !paramsRes.ok) throw new Error("Failed to fetch initial data.");            const meta = await metaRes.json();            versionDisplay.textContent = `v${meta.version}`;            const enginList = await enginRes.json();            convertEnginSelect.innerHTML = '<option value="identity"></option>';            enginList.forEach(engin => {                const option = document.createElement('option');                option.value = engin;                convertEnginSelect.appendChild(option);            });            defaultParams = await paramsRes.json();        } catch (error) {            console.error("Initialization failed:", error);            alert(getText('init_failed_alert'));            return;        }        const settingsToRestore = [            {element: workflowTypeSelect, key: 'translator_last_workflow', defaultValue: 'markdown_based'},            {                element: autoWorkflowSwitch,                key: 'translator_auto_workflow_enabled',                defaultValue: 'true',                type: 'boolean'            },            {element: txtInsertModeSelect, key: 'translator_txt_insert_mode', defaultValue: 'replace'},            {element: txtSeparatorInput, key: 'translator_txt_separator', defaultValue: '\\n'},            {element: xlsxInsertModeSelect, key: 'translator_xlsx_insert_mode', defaultValue: 'replace'},            {element: xlsxSeparatorInput, key: 'translator_xlsx_separator', defaultValue: '\\n'},            {element: xlsxTranslateRegionsTextarea, key: 'translator_xlsx_translate_regions', defaultValue: ''},            {element: docxInsertModeSelect, key: 'translator_docx_insert_mode', defaultValue: 'replace'},            {element: docxSeparatorInput, key: 'translator_docx_separator', defaultValue: ''}, // MODIFIED HERE            {element: srtInsertModeSelect, key: 'translator_srt_insert_mode', defaultValue: 'replace'},            {element: srtSeparatorInput, key: 'translator_srt_separator', defaultValue: '\\n'},            {element: epubInsertModeSelect, key: 'translator_epub_insert_mode', defaultValue: 'replace'},            {element: epubSeparatorInput, key: 'translator_epub_separator', defaultValue: ''}, // MODIFIED HERE            {element: htmlInsertModeSelect, key: 'translator_html_insert_mode', defaultValue: 'replace'},            {element: htmlSeparatorInput, key: 'translator_html_separator', defaultValue: ''}, // MODIFIED HERE            {element: assInsertModeSelect, key: 'translator_ass_insert_mode', defaultValue: 'replace'},            {element: assSeparatorInput, key: 'translator_ass_separator', defaultValue: '\\N'},            {element: jsonPathsTextarea, key: 'translator_json_paths', defaultValue: ''},            {element: skipTranslationSwitch, key: 'translator_skip_translate', defaultValue: 'false', type: 'boolean'},            {                element: systemProxyEnableSwitch,                key: 'translator_system_proxy_enable',                defaultValue: 'false',                type: 'boolean'            },            {                element: platformSelect,                key: 'translator_platform_last_platform',                defaultValue: 'https://api.302.ai/v1'            },            {element: convertEnginSelect, key: 'translator_convert_engin', defaultValue: 'mineru'},            {element: modelVersionSelect, key: 'translator_model_version', defaultValue: 'vlm'},            {element: mineruDeployBaseUrlInput, key: 'mineru_deploy_base_url', defaultValue: 'http://127.0.0.1:8000'},            {element: mineruDeployBackendSelect, key: 'mineru_deploy_backend', defaultValue: 'pipeline'},            {element: mineruDeployStartPageInput, key: 'mineru_deploy_start_page', defaultValue: '0'},            {element: mineruDeployEndPageInput, key: 'mineru_deploy_end_page', defaultValue: '99999'},            {                element: mineruDeployFormulaEnableSwitch,                key: 'mineru_deploy_formula_enable',                defaultValue: 'true',                type: 'boolean'            },            {element: toLangSelect, key: 'translator_to_lang', defaultValue: 'Simplified Chinese'},            {element: customLangInput, key: 'translator_custom_to_lang', defaultValue: ''},            {element: formulaCheckbox, key: 'translator_formula_ocr', defaultValue: 'true', type: 'boolean'},            {element: codeCheckbox, key: 'translator_code_ocr', defaultValue: 'true', type: 'boolean'},            {element: customPromptTranslateArea, key: 'custom_prompt', defaultValue: ''},            {                type: 'radio',                selector: '#thinkingModeBtnGroup input[name="thinking"]',                key: 'translator_thinking_mode',                defaultValue: 'disable'            },            // Glossary Gen Settings            {                element: glossaryGenerateEnableSwitch,                key: 'glossary_generate_enable',                defaultValue: 'false',                type: 'boolean'            },            {                type: 'radio',                selector: 'input[name="glossary_agent_config_choice"]',                key: 'glossary_agent_config_choice',                defaultValue: 'same'            },            {                element: glossaryAgentSystemProxyEnableSwitch,                key: 'glossary_agent_system_proxy_enable',                defaultValue: 'false',                type: 'boolean'            },            {element: glossaryAgentToLangSelect, key: 'glossary_agent_to_lang', defaultValue: 'Simplified Chinese'},            {element: glossaryAgentCustomLangInput, key: 'glossary_agent_custom_to_lang', defaultValue: ''},            {element: glossaryAgentCustomPromptTextarea, key: 'glossary_agent_custom_prompt', defaultValue: ''},            {                type: 'radio',                selector: 'input[name="glossary_agent_thinking"]',                key: 'glossary_agent_thinking_mode',                defaultValue: 'disable'            }        ];        restoreSettings(settingsToRestore);        // Step 4: Initialize i18n system. Now it is safe to call.        initI18n();        // Initial UI updates        updateWorkflowUI();        mainPlatformUpdater();        updateConvertEnginUI();        updateCustomLangUI(toLangSelect, customLangGroup, customLangInput);        updateTranslationModeUI();        setupGlossaryAgentPlatformUI();        updateGlossaryGenUI();        updateCustomLangUI(glossaryAgentToLangSelect, glossaryAgentCustomLangGroup, glossaryAgentCustomLangInput);        // Setup sliders        setupSlider(chunkSizeSlider, chunkSizeDisplay, chunkSizeReset, 'chunk_size', defaultParams);        setupSlider(concurrentSlider, concurrentDisplay, concurrentReset, 'concurrent', defaultParams);        setupSlider(temperatureSlider, temperatureDisplay, temperatureReset, 'temperature', defaultParams);        setupSlider(retrySlider, retryDisplay, retryReset, 'retry', defaultParams);        const agentDefaultParams = { // Agent sliders default to main config defaults            'glossary_agent_temperature': defaultParams.temperature,            'glossary_agent_chunk_size': defaultParams.chunk_size,            'glossary_agent_concurrent': defaultParams.concurrent,            'glossary_agent_retry': defaultParams.retry        };        setupSlider(glossaryAgentTemperatureSlider, glossaryAgentTemperatureDisplay, glossaryAgentTemperatureReset, 'glossary_agent_temperature', agentDefaultParams);        setupSlider(glossaryAgentChunkSizeSlider, glossaryAgentChunkSizeDisplay, glossaryAgentChunkSizeReset, 'glossary_agent_chunk_size', agentDefaultParams);        setupSlider(glossaryAgentConcurrentSlider, glossaryAgentConcurrentDisplay, glossaryAgentConcurrentReset, 'glossary_agent_concurrent', agentDefaultParams);        setupSlider(glossaryAgentRetrySlider, glossaryAgentRetryDisplay, glossaryAgentRetryReset, 'glossary_agent_retry', agentDefaultParams);        // Setup event listeners        const eventListenersConfig = [            // Main settings with save logic and UI updates            {element: workflowTypeSelect, event: 'change', handler: updateWorkflowUI},            {element: autoWorkflowSwitch, event: 'change', saveKey: 'translator_auto_workflow_enabled'},            {                element: txtInsertModeSelect,                event: 'change',                saveKey: 'translator_txt_insert_mode',                postHook: () => updateSeparatorVisibility(txtInsertModeSelect, txtSeparatorGroup)            },            {                element: xlsxInsertModeSelect,                event: 'change',                saveKey: 'translator_xlsx_insert_mode',                postHook: () => updateSeparatorVisibility(xlsxInsertModeSelect, xlsxSeparatorGroup)            },            {                element: docxInsertModeSelect,                event: 'change',                saveKey: 'translator_docx_insert_mode',                postHook: () => updateSeparatorVisibility(docxInsertModeSelect, docxSeparatorGroup)            },            {                element: srtInsertModeSelect,                event: 'change',                saveKey: 'translator_srt_insert_mode',                postHook: () => updateSeparatorVisibility(srtInsertModeSelect, srtSeparatorGroup)            },            {                element: epubInsertModeSelect,                event: 'change',                saveKey: 'translator_epub_insert_mode',                postHook: () => updateSeparatorVisibility(epubInsertModeSelect, epubSeparatorGroup)            },            {                element: htmlInsertModeSelect,                event: 'change',                saveKey: 'translator_html_insert_mode',                postHook: () => updateSeparatorVisibility(htmlInsertModeSelect, htmlSeparatorGroup)            },            {                element: assInsertModeSelect,                event: 'change',                saveKey: 'translator_ass_insert_mode',                postHook: () => updateSeparatorVisibility(assInsertModeSelect, assSeparatorGroup)            },            {                element: skipTranslationSwitch,                event: 'change',                saveKey: 'translator_skip_translate',                postHook: updateTranslationModeUI            },            {element: systemProxyEnableSwitch, event: 'change', saveKey: 'translator_system_proxy_enable'},            {element: platformSelect, event: 'change', handler: mainPlatformUpdater},            {element: convertEnginSelect, event: 'change', handler: () => updateConvertEnginUI(false)},            {                element: toLangSelect,                event: 'change',                saveKey: 'translator_to_lang',                postHook: () => updateCustomLangUI(toLangSelect, customLangGroup, customLangInput)            },            // Simple value savers            {element: txtSeparatorInput, event: 'input', saveKey: 'translator_txt_separator'},            {element: xlsxSeparatorInput, event: 'input', saveKey: 'translator_xlsx_separator'},            {element: xlsxTranslateRegionsTextarea, event: 'input', saveKey: 'translator_xlsx_translate_regions'},            {element: docxSeparatorInput, event: 'input', saveKey: 'translator_docx_separator'},            {element: srtSeparatorInput, event: 'input', saveKey: 'translator_srt_separator'},            {element: epubSeparatorInput, event: 'input', saveKey: 'translator_epub_separator'},            {element: htmlSeparatorInput, event: 'input', saveKey: 'translator_html_separator'},            {element: assSeparatorInput, event: 'input', saveKey: 'translator_ass_separator'},            {element: jsonPathsTextarea, event: 'input', saveKey: 'translator_json_paths'},            {                element: apikeyInput,                event: 'input',                handler: e => saveToStorage(`translator_platform_${platformSelect.value}_apikey`, e.target.value)            },            {                element: modelInput,                event: 'input',                handler: e => saveToStorage(`translator_platform_${platformSelect.value}_model_id`, e.target.value)            },            {                element: baseUrlInput, event: 'input', handler: e => {                    const newValue = e.target.value;                    // 新增下面这行来更新显示区域                    mainBaseUrlDisplay.textContent = newValue;                    if (platformSelect.value === 'custom') {                        saveToStorage('translator_platform_custom_base_url', newValue);                    }                }            },            {element: mineruTokenInput, event: 'input', saveKey: 'translator_mineru_token'},            {element: modelVersionSelect, event: 'change', saveKey: 'translator_model_version'},            {element: mineruDeployBaseUrlInput, event: 'input', saveKey: 'mineru_deploy_base_url'},            {element: mineruDeployBackendSelect, event: 'change', saveKey: 'mineru_deploy_backend'},            {element: mineruDeployStartPageInput, event: 'input', saveKey: 'mineru_deploy_start_page'},            {element: mineruDeployEndPageInput, event: 'input', saveKey: 'mineru_deploy_end_page'},            {element: mineruDeployFormulaEnableSwitch, event: 'change', saveKey: 'mineru_deploy_formula_enable'},            {element: customLangInput, event: 'input', saveKey: 'translator_custom_to_lang'},            {element: formulaCheckbox, event: 'change', saveKey: 'translator_formula_ocr'},            {element: codeCheckbox, event: 'change', saveKey: 'translator_code_ocr'},            {element: customPromptTranslateArea, event: 'input', saveKey: 'custom_prompt'},            // Glossary Gen event listeners            {                element: glossaryGenerateEnableSwitch,                event: 'change',                saveKey: 'glossary_generate_enable',                postHook: updateGlossaryGenUI            },            {                element: glossaryAgentSystemProxyEnableSwitch,                event: 'change',                saveKey: 'glossary_agent_system_proxy_enable'            },            {element: glossaryAgentPlatformSelect, event: 'change', handler: glossaryAgentPlatformUpdater},            {                element: glossaryAgentBaseUrlInput, event: 'input', handler: e => {                    const newValue = e.target.value;                    // 新增下面这行来更新术语表的显示区域                    glossaryBaseUrlDisplay.textContent = newValue;                    if (glossaryAgentPlatformSelect.value === 'custom') {                        saveToStorage('glossary_agent_platform_custom_baseurl', newValue);                    }                }            },            {                element: glossaryAgentKeyInput,                event: 'input',                handler: e => saveToStorage(`glossary_agent_platform_${glossaryAgentPlatformSelect.value}_apikey`, e.target.value)            },            {                element: glossaryAgentModelIdInput,                event: 'input',                handler: e => saveToStorage(`glossary_agent_platform_${glossaryAgentPlatformSelect.value}_model_id`, e.target.value)            },            {                element: glossaryAgentToLangSelect,                event: 'change',                saveKey: 'glossary_agent_to_lang',                postHook: () => updateCustomLangUI(glossaryAgentToLangSelect, glossaryAgentCustomLangGroup, glossaryAgentCustomLangInput)            },            {element: glossaryAgentCustomLangInput, event: 'input', saveKey: 'glossary_agent_custom_to_lang'},            {element: glossaryAgentCustomPromptTextarea, event: 'input', saveKey: 'glossary_agent_custom_prompt'},            // Others            {element: glossaryFilesInput, event: 'change', handler: handleGlossaryFiles},            {                element: viewGlossaryBtn, event: 'click', handler: () => {                    populateGlossaryModal();                    glossaryModal.show();                }            },            {element: clearGlossaryBtn, event: 'click', handler: clearGlossary},            {element: addNewTaskBtn, event: 'click', handler: () => createTaskCard()},            {element: setBilingualViewBtn, event: 'click', handler: () => setPreviewDisplayMode('bilingual')},            {element: setTranslatedOnlyViewBtn, event: 'click', handler: () => setPreviewDisplayMode('translatedOnly')},        ];        setupEventListeners(eventListenersConfig);        // Setup radio button groups separately as they are collections        document.querySelectorAll('#thinkingModeBtnGroup input[name="thinking"]').forEach(radio => radio.addEventListener('change', e => saveToStorage('translator_thinking_mode', e.target.value)));        glossaryAgentConfigChoiceRadios.forEach(radio => radio.addEventListener('change', e => {            saveToStorage('glossary_agent_config_choice', e.target.value);            updateGlossaryCustomConfigUI();        }));        glossaryAgentThinkingRadios.forEach(radio => radio.addEventListener('change', e => saveToStorage('glossary_agent_thinking_mode', e.target.value)));        document.querySelectorAll('.toggle-password').forEach(setupPasswordToggle);        // [MODIFIED] Setup synchronized scrolling for the preview panes        setupSyncScrollListeners();        if (isAdminMode) {            document.title = "DocuTranslate - Admin Panel";            try {                const response = await fetch('/service/task-list');                if (!response.ok) throw new Error(`Failed to fetch task list: ${response.statusText}`);                const allTaskIds = await response.json();                if (allTaskIds && allTaskIds.length > 0) {                    allTaskIds.reverse().forEach(taskId => createTaskCard(taskId, true));                }            } catch (error) {                console.error("Admin mode: Failed to load task list.", error);                alert(getText('admin_tasklist_failed'));            }            updateTaskPlaceholderVisibility();        } else {            const savedTaskIds = JSON.parse(getFromStorage('active_task_ids', '[]'));            if (savedTaskIds.length > 0) {                savedTaskIds.forEach(taskId => createTaskCard(taskId, true));            } else {                createTaskCard();            }        }        // Add resize listener for preview        window.addEventListener('resize', () => {            if (previewOffcanvasEl.classList.contains('show')) {                const currentMode = originalPreviewContainer.style.display !== 'none' ? 'bilingual' : 'translatedOnly';                setPreviewDisplayMode(currentMode);            }        });    }    // --- [NEW & CORRECTED] CONFIG IMPORT/EXPORT LOGIC ---    function collectAllConfigs() {        const config = {};        const getPlatformMaps = (selectElement, prefix) => {            const apiKeyMap = {};            const modelIdMap = {};            Array.from(selectElement.options).forEach(option => {                const platformValue = option.value;                if (platformValue) {                    const apiKey = getFromStorage(`${prefix}_${platformValue}_apikey`, '');                    const modelId = getFromStorage(`${prefix}_${platformValue}_model_id`, '');                    if (apiKey) apiKeyMap[platformValue] = apiKey;                    if (modelId) modelIdMap[platformValue] = modelId;                }            });            return {apiKeyMap, modelIdMap};        };        config.workflow = {            workflow_type: workflowTypeSelect.value,            auto_workflow_enabled: autoWorkflowSwitch.checked,        };        config.parser = {            convert_engine: convertEnginSelect.value,            mineru_token: getFromStorage('translator_mineru_token', ''),            model_version: modelVersionSelect.value,            formula_ocr: formulaCheckbox.checked,            code_ocr: codeCheckbox.checked,            // New deploy settings            mineru_deploy_base_url: mineruDeployBaseUrlInput.value,            mineru_deploy_backend: mineruDeployBackendSelect.value,            mineru_deploy_start_page: parseInt(mineruDeployStartPageInput.value, 10),            mineru_deploy_end_page: parseInt(mineruDeployEndPageInput.value, 10),            mineru_deploy_formula_enable: mineruDeployFormulaEnableSwitch.checked,        };        const mainPlatformMaps = getPlatformMaps(platformSelect, 'translator_platform');        config.translator = {            skip_translation: skipTranslationSwitch.checked,            system_proxy_enable: systemProxyEnableSwitch.checked,            platform: {                selected_platform: platformSelect.value,                custom_base_url: getFromStorage('translator_platform_custom_base_url', ''),                api_key_map: mainPlatformMaps.apiKeyMap,                model_id_map: mainPlatformMaps.modelIdMap,            },            target_language: toLangSelect.value,            custom_target_language: customLangInput.value,            thinking_mode: document.querySelector('input[name="thinking"]:checked')?.value || 'disable',            custom_prompt: customPromptTranslateArea.value,            chunk_size: parseInt(chunkSizeSlider.value, 10),            concurrent_requests: parseInt(concurrentSlider.value, 10),            temperature: parseFloat(temperatureSlider.value),            retries: parseInt(retrySlider.value, 10),        };        const glossaryPlatformMaps = getPlatformMaps(glossaryAgentPlatformSelect, 'glossary_agent_platform');        config.glossary = {            generate_enable: glossaryGenerateEnableSwitch.checked,            agent_config_choice: document.querySelector('input[name="glossary_agent_config_choice"]:checked')?.value || 'same',            agent_custom_prompt: glossaryAgentCustomPromptTextarea.value,            agent_config: {                system_proxy_enable: glossaryAgentSystemProxyEnableSwitch.checked,                target_language: glossaryAgentToLangSelect.value,                custom_target_language: glossaryAgentCustomLangInput.value,                thinking_mode: document.querySelector('input[name="glossary_agent_thinking"]:checked')?.value || 'default',                chunk_size: parseInt(glossaryAgentChunkSizeSlider.value, 10),                concurrent_requests: parseInt(glossaryAgentConcurrentSlider.value, 10),                temperature: parseFloat(glossaryAgentTemperatureSlider.value),                retries: parseInt(glossaryAgentRetrySlider.value, 10),                platform: {                    selected_platform: glossaryAgentPlatformSelect.value,                    custom_base_url: getFromStorage('glossary_agent_platform_custom_baseurl', ''),                    api_key_map: glossaryPlatformMaps.apiKeyMap,                    model_id_map: glossaryPlatformMaps.modelIdMap,                }            }        };        config.workflow_options = {            txt: {insert_mode: txtInsertModeSelect.value, separator: txtSeparatorInput.value},            docx: {insert_mode: docxInsertModeSelect.value, separator: docxSeparatorInput.value},            xlsx: {                insert_mode: xlsxInsertModeSelect.value,                separator: xlsxSeparatorInput.value,                translate_regions: xlsxTranslateRegionsTextarea.value            },            srt: {insert_mode: srtInsertModeSelect.value, separator: srtSeparatorInput.value},            epub: {insert_mode: epubInsertModeSelect.value, separator: epubSeparatorInput.value},            html: {insert_mode: htmlInsertModeSelect.value, separator: htmlSeparatorInput.value},            ass: {insert_mode: assInsertModeSelect.value, separator: assSeparatorInput.value},            json: {json_paths: jsonPathsTextarea.value},        };        return config;    }    function applyAllConfigs(config) {        const apply = (value, func) => {            if (value !== undefined && value !== null) {                try {                    func(value);                } catch (e) {                    console.warn("Failed to apply setting value:", value, e);                }            }        };        const applyPlatformMaps = (platformData, prefix) => {            if (!platformData) return;            apply(platformData.api_key_map, map => Object.entries(map).forEach(([p, k]) => saveToStorage(`${prefix}_${p}_apikey`, k)));            apply(platformData.model_id_map, map => Object.entries(map).forEach(([p, m]) => saveToStorage(`${prefix}_${p}_model_id`, m)));            apply(platformData.custom_base_url, v => saveToStorage(`${prefix}_custom_base_url`, v));        };        const selectRadio = (name, value) => {            apply(value, v => {                const radio = document.querySelector(`input[name="${name}"][value="${v}"]`);                if (radio) radio.checked = true;            });        };        apply(config.workflow, s => {            apply(s.workflow_type, v => workflowTypeSelect.value = v);            apply(s.auto_workflow_enabled, v => autoWorkflowSwitch.checked = v);        });        apply(config.parser, s => {            apply(s.convert_engine, v => convertEnginSelect.value = v);            apply(s.mineru_token, v => {                mineruTokenInput.value = v;                saveToStorage('translator_mineru_token', v);            });            apply(s.model_version, v => modelVersionSelect.value = v);            apply(s.formula_ocr, v => formulaCheckbox.checked = v);            apply(s.code_ocr, v => codeCheckbox.checked = v);            // New deploy settings            apply(s.mineru_deploy_base_url, v => mineruDeployBaseUrlInput.value = v);            apply(s.mineru_deploy_backend, v => mineruDeployBackendSelect.value = v);            apply(s.mineru_deploy_start_page, v => mineruDeployStartPageInput.value = v);            apply(s.mineru_deploy_end_page, v => mineruDeployEndPageInput.value = v);            apply(s.mineru_deploy_formula_enable, v => mineruDeployFormulaEnableSwitch.checked = v);        });        apply(config.translator, s => {            applyPlatformMaps(s.platform, 'translator_platform');            apply(s.platform?.selected_platform, v => platformSelect.value = v);            apply(s.skip_translation, v => skipTranslationSwitch.checked = v);            apply(s.system_proxy_enable, v => systemProxyEnableSwitch.checked = v);            apply(s.target_language, v => toLangSelect.value = v);            apply(s.custom_target_language, v => customLangInput.value = v);            selectRadio('thinking', s.thinking_mode);            apply(s.custom_prompt, v => customPromptTranslateArea.value = v);            apply(s.chunk_size, v => chunkSizeSlider.value = v);            apply(s.concurrent_requests, v => concurrentSlider.value = v);            apply(s.temperature, v => temperatureSlider.value = v);            apply(s.retries, v => retrySlider.value = v);        });        apply(config.glossary, s => {            apply(s.generate_enable, v => glossaryGenerateEnableSwitch.checked = v);            selectRadio('glossary_agent_config_choice', s.agent_config_choice);            apply(s.agent_custom_prompt, v => glossaryAgentCustomPromptTextarea.value = v);            apply(s.agent_config, ac => {                apply(ac.system_proxy_enable, v => glossaryAgentSystemProxyEnableSwitch.checked = v);                apply(ac.target_language, v => glossaryAgentToLangSelect.value = v);                apply(ac.custom_target_language, v => glossaryAgentCustomLangInput.value = v);                selectRadio('glossary_agent_thinking', ac.thinking_mode);                apply(ac.chunk_size, v => glossaryAgentChunkSizeSlider.value = v);                apply(ac.concurrent_requests, v => glossaryAgentConcurrentSlider.value = v);                apply(ac.temperature, v => glossaryAgentTemperatureSlider.value = v);                apply(ac.retries, v => glossaryAgentRetrySlider.value = v);                applyPlatformMaps(ac.platform, 'glossary_agent_platform');                apply(ac.platform?.selected_platform, v => glossaryAgentPlatformSelect.value = v);            });        });        apply(config.workflow_options, s => {            apply(s.txt, v => {                apply(v.insert_mode, m => txtInsertModeSelect.value = m);                apply(v.separator, sp => txtSeparatorInput.value = sp);            });            apply(s.docx, v => {                apply(v.insert_mode, m => docxInsertModeSelect.value = m);                apply(v.separator, sp => docxSeparatorInput.value = sp);            });            apply(s.xlsx, v => {                apply(v.insert_mode, m => xlsxInsertModeSelect.value = m);                apply(v.separator, sp => xlsxSeparatorInput.value = sp);                apply(v.translate_regions, tr => xlsxTranslateRegionsTextarea.value = tr);            });            apply(s.srt, v => {                apply(v.insert_mode, m => srtInsertModeSelect.value = m);                apply(v.separator, sp => srtSeparatorInput.value = sp);            });            apply(s.epub, v => {                apply(v.insert_mode, m => epubInsertModeSelect.value = m);                apply(v.separator, sp => epubSeparatorInput.value = sp);            });            apply(s.html, v => {                apply(v.insert_mode, m => htmlInsertModeSelect.value = m);                apply(v.separator, sp => htmlSeparatorInput.value = sp);            });            apply(s.ass, v => {                apply(v.insert_mode, m => assInsertModeSelect.value = m);                apply(v.separator, sp => assSeparatorInput.value = sp);            });            apply(s.json, v => {                apply(v.json_paths, jp => jsonPathsTextarea.value = jp);            });        });        // --- Trigger all UI update functions to reflect changes ---        updateWorkflowUI();        mainPlatformUpdater();        updateConvertEnginUI(false);        updateTranslationModeUI();        updateCustomLangUI(toLangSelect, customLangGroup, customLangInput);        updateGlossaryGenUI();        glossaryAgentPlatformUpdater();        updateCustomLangUI(glossaryAgentToLangSelect, glossaryAgentCustomLangGroup, glossaryAgentCustomLangInput);        // Trigger slider "input" events to update their display text and reset button visibility        [            chunkSizeSlider, concurrentSlider, temperatureSlider, retrySlider,            glossaryAgentChunkSizeSlider, glossaryAgentConcurrentSlider, glossaryAgentTemperatureSlider, glossaryAgentRetrySlider        ].forEach(slider => slider.dispatchEvent(new Event('input')));        // Trigger change/input events on all form elements to ensure local storage is updated        settingsForm.querySelectorAll('input, select, textarea').forEach(el => {            el.dispatchEvent(new Event('change', {bubbles: true}));            el.dispatchEvent(new Event('input', {bubbles: true}));        });    }    /**     * Handles the click event for the Export button.     */    function handleExportConfig() {        const config = collectAllConfigs();        const configString = JSON.stringify(config, null, 2);        const blob = new Blob([configString], {type: 'application/json'});        const url = URL.createObjectURL(blob);        const a = document.createElement('a');        a.href = url;        a.download = 'config.json';        document.body.appendChild(a);        a.click();        document.body.removeChild(a);        URL.revokeObjectURL(url);    }    /**     * Handles the file selection for the Import button.     * @param {Event} event The file input change event.     */    function handleImportConfig(event) {        const file = event.target.files[0];        if (!file) return;        const reader = new FileReader();        reader.onload = (e) => {            try {                const config = JSON.parse(e.target.result);                applyAllConfigs(config);                alert(getText('configImportSuccess'));            } catch (error) {                console.error("Failed to parse config file:", error);                alert(getText('configImportError'));            }            // Reset file input to allow re-importing the same file again            event.target.value = '';        };        reader.readAsText(file);    }    // Add event listeners for the new buttons in the init function    const importConfigBtn = document.getElementById('importConfigBtn');    const exportConfigBtn = document.getElementById('exportConfigBtn');    const configFileInput = document.getElementById('configFileInput');    importConfigBtn.addEventListener('click', () => configFileInput.click());    exportConfigBtn.addEventListener('click', handleExportConfig);    configFileInput.addEventListener('change', handleImportConfig);    // --- END OF NEW CODE BLOCK ---    // --- Theme switcher logic ---    const getPreferredTheme = () => {        const storedTheme = localStorage.getItem('theme');        if (storedTheme) {            return storedTheme;        }        return 'auto';    };    const setTheme = theme => {        if (theme === 'auto') {            document.documentElement.setAttribute('data-bs-theme', window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');        } else {            document.documentElement.setAttribute('data-bs-theme', theme);        }    };    const showActiveTheme = (theme) => {        document.querySelectorAll('[data-bs-theme-value]').forEach(element => {            element.classList.remove('active');        });        const activeButton = document.querySelector(`[data-bs-theme-value="${theme}"]`);        if (activeButton) {            activeButton.classList.add('active');        }    };    const preferredTheme = getPreferredTheme();    setTheme(preferredTheme);    showActiveTheme(preferredTheme);    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {        const storedTheme = localStorage.getItem('theme');        if (storedTheme === 'auto' || !storedTheme) {            setTheme('auto');        }    });    document.querySelectorAll('[data-bs-theme-value]').forEach(toggle => {        toggle.addEventListener('click', () => {            const theme = toggle.getAttribute('data-bs-theme-value');            localStorage.setItem('theme', theme);            setTheme(theme);            showActiveTheme(theme);        });    });    // --- Start the application ---    document.addEventListener('DOMContentLoaded', init);</script></body></html>